-
Notifications
You must be signed in to change notification settings - Fork 2
dev blogs, 2014
지난 CRF 실험의 오류를 발견하고 정상 결과에 만족한 이후로 또 게을러져서 별 진척이 없다가 이렇게 오랜만에 포스팅을 합니다.
우선 현재의 세그먼트가 미리 결정되어 있는 구조에서는 별로 도움이 되지 않겠지만, 추후에 형태소의 분할 과정에서 추정 로직이 들어갈 경우 도움이 될 만한 자질을 한번 추가해 봤습니다.
-
이전 형태소의 종성 유무: 이전 형태소에 ㄹ종성으로 끝날경우 'PFC=ㄹ', 그 외의 종성으로 끝날 경우 'PFC' 자질을 추가해 줍니다. PFC는 Previous Final Consonant의 약자입니다.
-
현재 형태소의 초성 유무: 현재 형태소에 자음으로 시작할 경우 'CIC' 자질을 추가해 줍니다. CIC는 Current Initial Consonant의 약자입니다.
이렇게 하면 사전에서 검색되지 않는 형태소(주로 신조어 명사가 되겠죠)에 대하여 1에서 3글자 정도로 추정을 하게 될텐데, 이때 도움이 되지 않을까 생각하여 근거 없이 본능적으로(?) 한번 넣어봤습니다. ^^;
그리고, hanal 라이브러리의 API는 당연히 UTF-8 인코딩으로 가겠지만 내부적으로는 wchar_t 및 std::wstring을 이용하여 구현할 생각입니다. 이를 위해 형태소 사전 구조도 wstring을 지원하는 TRIE를 사용해야 겠지요. 제가 사용하려고 하는 자료구조는 CRFsuite를 만든 Naoaki Okazaki 교수님의 DASTrie를 사용하려 했는데, 좀 살펴보니 wstring 지원 여부가 좀 불투명해 보여서 걱정입니다.
예전에 int32_t 타입을 이용한 trie 자료구조를 만들 일이 있어서 한번 구현해 본 적은 있지만, 아직 실력이 미천하여 double array trie와 같이 속도와 메모리에 효율적으로 구현하지는 못했었습니다. 혹시 wstring을 지원하는 효율적인 trie 라이브러리를 알고계시면 제보(?) 바랍니다.
이전 한글날 학회 즈음에 올린 포스팅에서 간단하게 자질을 뽑아 CRF를 이용하여 실험을 했고 결과가 이상하게도 60% 언저리로 나온다고 말씀드린 바 있습니다. 분명 HMM에 비하면 풍부한 자질을 사용하는데 성능이 이상하게 나오지 않는다고 말씀드렸었는데요, 실험에 중요한 오류가 있었습니다. ㅠ.ㅠ
저는 CRF를 적용할 때 CRFsuite라는 툴킷을 즐겨 사용합니다. 비슷하게 유명한 CRF++라는 툴킷에 비해 속도는 한 10배쯤(정확한 건 아니고 채감상 입니다. ^^) 빠른데 반해 성능은 0.1% 언저리로 별로 차이가 나지 않기 때문입니다. 그런데 이 툴킷은 자질 간의 구분자로 공백은 안되고 반드시 탭을 이용해야 합니다. 여기에 공백을 자질 구분자로 넣어서 학습을 했으니 학습은 산으로 갔고, 결과는 바닥으로 갔지요. ㅎㅎ
초기 오류가 있는 실험 결과에서 멘붕을 한번 겪고나서 한 3개월은 회사가 바쁘다는 핑계로 거의 손도 못대고 있었습니다만, 이렇게 운이 좋게도 실험의 문제점을 발견하고 10% 언저리로 샘플링해서 5-fold cross validation을 진행해본 결과, 98% 언저리의 성능이 나옵니다. 이정도면 자질로 지지고 볶아서(?) 성능을 뽑아내는 작업은 굳이 하지 않고 간단한 모델로 가도 될 것 같습니다.
매년 한글날을 전후하여 한글 및 한국어 정보처리 학술대회가 열립니다. 올해도 어김 없이 10월 10, 11일 양일간 춘천의 강원대학교에서 열립니다. (줄여서 한글날 학회라고 하지요.)
사실 처음 hanal 프로젝트를 시작한 5월에는 야심차게 올해 한글날 학회에 발표하려고 마음 먹었었습니다. 그러나 제가 형태소 분석기를 너무 얕잡아본 것도 있고 생각보다 회사 상황이 녹녹지 못해 창피하게도 이렇다할 결과물이 없네요.
원래 오픈소스 프로젝트가 잉여의 산물 아니겠습니까? 지나갔다고 포기할 수야 없지 않겠습니까? 1년 벌었다 생각하고 천천히 더 내실있게 하면 되지요. 내년 한글날 학회에 뵙겠습니다. ㅎㅎ
지난번 표층형-형태소 정렬 이후 간단하게 나마 자질을 뽑아 CRF 모델 학습을 한번 해봤습니다. 그래도 HMM 보다는 많은 자질을 사용하니 그럭저럭 결과가 나오려니 했는데, 학습이 끝나질 않았습니다. 그래서 부랴부랴 MaxEnt를 이용해 분류기로 학습해서 적용해 봤더니 정확도가 92% 정도 밖에 안나오더군요. 아래는 학습에 사용한 자질들입니다.
- 형태소: 이전, 현재, 다음
- 표층형: 이전, 현재, 다음
- 어절의 처음과 끝: LSP(Left SPace), RSP(Right SPace)
- 문장 처음과 끝: BOS(Begin Of Sentence), EOS(End Of Sentence)
그래서 CRF를 학습을 다시 돌려놓고 끝까지 지켜봤더니 3일을 넘게 걸려서 모델을 만들어 냈습니다. 그리고 나서 태깅을 시도해 보니 60% 언저리의 정확도가 나오더군요. ㅠ.ㅠ 그렇게 멘붕을 겪고 지금은 자질을 전부 구현하는 것과, 학습 코퍼스를 적절히 줄여서 시도해 보려고 합니다.
처음 hanal 프로젝트를 시작한 것이 5월 중순이었으니 벌써 2개월이 되어갑니다. 그런데 지금까지 한 것이라고는 코퍼스에서 표층형-형태소 정렬 밖에 없습니다. 이래가지고서야 어디 프로젝트가 10년 안에 끝날런지요? ㅠ.ㅠ 한국어에 워낙에 축약이나 예외들이 많아 생각만큼 정렬이 되지 않은 점도 있고, 세종 코퍼스에 의외로 오류가 많은 것도 한 몫 한 것 같습니다. 이쯤에서 정렬 방법에 대해 한번 정리하고 다음 단계로 넘어가도록 하겠습니다.
예를들어 예/NNG + 를/JKO + 들/VV + 어/EC
이 경우는 별로 어려울 것이 없습니다. 맨 앞 형태소부터 어절의 일치하는 앞부분만 떼어내어 정렬을 하면 됩니다. 똑같은 방식으로 맨 뒤 형태소부터 어절의 일치하는 뒷부분을 떼어내며 정렬을 해나갈 수도 있습니다. 앞에서 부터 정렬을 forward, 뒤에서 부터 정렬을 backward라고 명명했습니다.
이렇게 해서 얻어진 결과는 다음과 같은 표층형 -> 형태소 사전입니다.
예 -> 예/NNG
를 -> 를/JKO
들 -> 들/VV
어 -> 어/EC
다양 -> 다양/XR
한 -> 하/XSA + ㄴ/ETM
이 사전은 나중에 원시 문장에 대해 최적의 형태소 분석 결과를 얻기 위한 후보를 생성하는 데 사용할 것입니다.
다양한 다양/XR + 하/XSA + ㄴ/ETM
이 경우 다양/XR
은 음절 단위로 일치하지만 하/XSA
부터 ㄴ/ETM
두 형태소는 한
과 일치하는 경우로 음절 단위 처리로는 정렬할 수가 없습니다. 따라서 자소 분해를 해서 정렬을 시도해야 합니다.
ㅎㅏㄴ ㅎㅏ/XSA + ㄴ/ETM
이때부터는 두개 이상의 자소 분해된 형태소를 결합하여 역시 자소 분해된 어절과 일치하는 지 여부를 판별해야 합니다. 앞에서부터 2개, 3개, ... 마지막 형태소까지 합쳐서 어절과의 정렬을 시도하고(forward), 마찬가지로 뒤에서부터(backward)도 정렬을 시도합니다.
음절 단위 및 자소 단위로 forward/backward 정렬을 수행해서 어절 내 모든 음절이 깔끔하게 정렬이 되면 좋겠지만 그렇지 않은 어절도 상당히 많습니다. 이렇게 forward/backward 정렬을 하고 남은 음절과 형태소를 샌드위치(sandwich) 음절, 형태소라고 명명했습니다. ^^;
주로 세종 코퍼스에서 오류인 경우가 많습니다. 마지막 마침표나 쉼표, 물결무늬 등이 형태소 분석열에서 빠져서 정렬이 되지 못하고 남는 경우인데요, 보정이 가능한 경우 보정을 해줬습니다.
다양한 케이스들이 발생하는데요, 규칙에 의해 적절히 남은 형태소를 앞쪽이나 뒤쪽 정렬에 붙여줍니다.
허락지 허락/NNG + 하/XSV + 지/EC
많은데다 많/VA + 은/ETM + 데/NNB + 에/JKB + 다/JX
- '이' 긍정지정사나 '하' 보조용언 뒤에 어미가 나올 경우
- '에' 부사격조사 뒤에 보조사가 나올 경우
- 'ㄹ' 관형형전성어미 앞에 용언이 나올 경우
- 연결어미나 종결어미 앞에 용언이 나올 경우
그 외 접미사류만 맨 뒤에 남은 경우 앞쪽 정렬에 붙여주는 식으로, 데이터를 보며 빈도가 높은 몇몇 케이스들에 대해 규칙을 추가해 주고 나머지는 버리기로 했습니다.
마지막으로 forward/backward 정렬을 수행하고 음절과 형태소 모두가 남는 경우가 있습니다.
이러한 목록(사전)에 등록하여 일치하면 그대로 정렬하는 방식으로 처리합니다. 아래와 같은 것들이 있습니다.
해 하/XSV + 아/EC
내 나/NP + 의/JKG
가령 아래와 같은 경우 해야
는 하/VV
와 아야/EC
로 정렬하고 나머지 하/VX
는 뒤에 오는 겠/EP
에 붙여주는 식으로 역시 빈도를 보며 적절한 처리를 해줬습니다.
해야 하/VV + 아야/EC + 하/VX
겠 겠/EP
남은 음절과 형태소 모두 자소 분해를 한 다음 편집 거리(Levenshtein Distance)를 구합니다. 둘 중 길이가 긴 것을 max_len
, 편집 거리를 dist
라 하면 유사도는 (max_len - dist) / max_len
이 됩니다.
쉬우나 쉽/VA + 으나/EC
ㅅㅟㅇㅜㄴㅏ ㅅㅟㅂ/VA + ㅇㅡㄴㅏ/EC
위에서 max_len은 7이고, dist는 2입니다. 따라서 유사도는 5 / 7 = 0.7143
이 되어 임계값 0.5보다 커서 정렬을 해줍니다.
처음에는 어절의 음절들과 형태소들 간에 모든 가능한 정렬에 대해 다이내믹 프로그램을 이용해 편집 거리를 최소로 하는 정렬을 찾는 문제로 풀면 되지 않을까 생각했습니다만, forward/backward로 일단 해보고 남은 걸로 하면 좀더 탐색 공간이 줄어들 걸로 판단하고 진행해보니, 남은 건 그냥 손으로 하면 되겠구나!라고 생각되어 이렇게 진행하게 되었습니다. 그러나 막상 정렬을 해보고 정렬이 안되고 남은 부분은 빈도를 추출하여 규칙을 만들어 넣고 다시 정렬을 해보고.. 결론적으로는 이렇게 반복적으로 규칙을 추가하는 지리한 과정이 되어 버렸습니다. 이쯤에서 멈추고 나머지 정렬이 안되는 어절을 포함하는 문장은 버리고 다음 단계로 가려 합니다.
P.S. 이번 정렬 작업을 진행하며 세종 코퍼스를 자세히 보게 되었는데, 듣던 데로 코퍼스에 오류가 상당히 많았습니다. 세종 코퍼스 오류 패치 프로젝트를 하나 만들어 다같이 수정 내용을 공유하는 것이 어떨까하는 생각이 듭니다. 세종 코퍼스에는 라인(어절) 별로 고유 아이디가 있는데요, 간단하게 파일 별로 공유 아이디와 수정한 내용만을 기입한 패치 파일을 작성하는 식으로 말이죠.
아마도 고려대 이도길 박사님이 박사학위 논문에서 처음 제시한 것으로 생각되는 음절단위 형태소 분석 방법은, 강원대 이창기 교수님의 아래 논문에서 Structural SVM을 사용하여 엄청난 성능과 함께, 심플하면서도 띄어쓰기 오류에도 강건한 깔끔한 모델링을 보여줍니다. http://kiise.or.kr/e_journal/2013/12/sa/pdf/09.pdf
처음에는 이 방법으로 한번 해볼까 생각했었습니다. 여기에 제가 잘 모르는 Structural SVM보다는 품사 태깅 분야에서 비교적 성능이 잘 입증된 CRF를 사용할 생각이었습니다. 그런데 가만 생각해 보니 세종 코퍼스로부터 학습한 결과가 음절 단위로 분해되어 있으면 사용자 사전과 같은 코퍼스에서 발견되지 않는 어휘들에 대해서는 어떻게 학습된 모델과 잘 융합할 수 있을 지 난감할 것 같았습니다. 논문을 위해서라면 주어진 코퍼스에서 정확한 품질을 내는 데에만 집중할 수도 있지만, 실용적인 관점에서 보자면 사용자는 도메인에 따라 사전을 적당히 추가하고 싶을 수도 있으니까요.
이러한 이유로 지금은 그냥 전통적인 방식데로 형태소 단위로 사전을 구축하는 것이 좋을 것 같다고 생각하고 있습니다. 그런데 우리말은 표층 형태와 형태소로 분해된 형태가 매우 다른 경우가 많습니다. 예를 들어 나섰다
와 같은 어절은 나서/VV + 었/EP + 다/EF
와 같이 나서 + 었
은 나섰
으로 축약이 됩니다. 따라서 표층 형태와 형태소 분석 결과를 다음과 같이 잘 정렬해 줄 필요가 있습니다.
나섰 --> 나서/VV + 었/EP
다 --> 다/EF
이러한 표층형과 형태소의 정렬을 통해 코퍼스에 나타난 모든 결과를 바탕으로 표층형-형태소 사전을 구축하고, 이 형태소 사전을 이용하여 형태소 분석 단계에서 가능한 모든 분석 결과를 도출하는 방향으로 진행하려 합니다. 형태소 사전의 경우 좌측 표층형 항목과 우측 형태소 항목으로 구성될텐데요, 되도록 우측 형태소 항목은 단일 형태소가 되도록 코퍼스에서 잘 정렬하는 것이 맨 먼저 해야할 작업일 것 같습니다.
오늘도 코드 한줄 없이 입개발로 마무리하는 하루였습니다. ^^;
hanal이란 이름은 첫 위키 페이지에서 짐작하실 수 있듯이 Hangul의 h와 ANALyzer의 anal을 따와서 합쳐진 이름입니다. 네, 지극히 공돌이 스런 이름이란 것은 인정합니다. 그래도 부르기 쉽고 어느정도 의미도 이름에 잘 부여된 것 같아 일단은 나쁘지 않은 이름 같습니다. 덧붙여 한글로 발음하게 되면 하날이 되는데요, 아래아를 한번 넣어서 90년대 대학가 동아리 이름 짓듯 아래한글 허세를 좀 부려볼까 하다가 참았습니다. ㅎㅎ 하날에 아래아가 붙은 것이 하늘의 중세 표현이라니 한글의미 또한 나쁘지 않습니다.
처음에는 세종이란 이름이 마음에 들어 어떻게 좀 비벼넣어 볼까 했지만 세종계획이란 프로젝트도 있고, 그 이름의 의미가 무겁기에 함부로 붙이기 어려웠습니다. 그래서 한글을 집현전의 어느 학자가 개발했다고 생각하고 그 사람의 이름을 좀 따볼까 하고 알아봤더니, 한글은 세종대왕께서 언어학의 깊은 조예를 바탕으로 직접 만드셨다는 사실을 알았습니다. 엄친아 킹왕짱이 셨습니다. --; 또 한분 국어학에 지대한 영향을 끼친 주시경 선생님의 호, '한힌샘'을 본딴 이름은 예전 회사에서 한번 써먹었고요. 어디서 좀 가져다 써볼랬던, 창의력이라곤 사전에 없던 공돌이는 멘붕에 빠져 결국 영어 앞글자를 조합해서 만들고야 말았습니다.
저는 언젠가부터 회사에서 일을 하면서도 프로젝트에 먼저 이름을 붙여주고 시작하는 것이 좋아졌습니다. 그래야 프로젝트에 애착도 가고 개발 중이나 완료 이후에도 명칭이 통일 되어 명확더군요. 이렇게 이름부터 냅다 정하고 github에 하나 만들고 보니 시작이 반이라고 벌써 다 된 것같은 이 근본 없는 뿌듯함은 뭘까요? ㅎㅎ 오늘도 코드 한 줄 없이 입개발만 하네요. 허허
한국어 형태소 분석기는 자연어처리 및 응용 분야에서 가장 먼저 수행되는 모듈입니다. 전국의 각 대학에 자연어처리 연구실들이 있고, 자연어처리를 하는 회사들도 굉장히 많은데, 대부분 형태소 분석기는 라이브러리 형태로 판매하는 것을 선호하며 오픈소스로 공개하는 경우는 별로 없습니다.
미국의 경우 Penn TreeBank 코퍼스의 태그셋으로 거의 표준화 되어 있고, Eric Brill의 태거, Tree Tagger 등 소스가 공개된 품사 태거를 어렵지 않게 구할 수 있습니다. 일본의 경우도 UniDic, IPAdic과 같이 널리 사용되는 태그셋 및 Chasen, MeCab과 같은 공개된 형태소 분석기가 존재합니다.
한국의 경우 대학 혹은 회사 마다 태그셋이 제 각각이고, 코퍼스 기반으로 형태소 분석기를 작성한 경우 그 코퍼스는 더더욱 구하기 힘든 것이 현실입니다. 1999년에 최초이자 마지막으로 개최된 MATEC 대회를 끝으로 한 10년 간 형태소 분석기에 관한 논문도 많이 발표되지 않았습니다. (이 기간 동안 기계번역 및 감성분석이 자연어처리 연구의 주류를 이루었던 듯 합니다.)
최근에야 21세기 세종계획 프로젝트가 10년 간의 성과물을 공개하기에 이르렀고, 학계에서도 이 세종 코퍼스를 이용하여 형태소 분석기를 실험하여 논문을 발표하고 있습니다. 세종 코퍼스는 구어체의 경우 약 80만 어절, 문어체의 경우 약 1,000만 어절이나 됩니다. KAIST에서도 약 100만 어절의 코퍼스를 공개했습니다만, 세종 코퍼스에 비하면 상대적으로 작은 편입니다.
저는 몇 년 전부터 세종 태그셋에 기반한 C++로 제작된 오픈소스 형태소 분석기를 만들어 보고 싶었습니다. 그리고 이것을 한글 및 한국어 정보처리 학술대회에 발표하여 널리 쓰였으면 하는 바램도 있었고요. 일본어에는 MeCab이라는 걸출한 오픈소스 형태소 분석기가 존재하는데 한국어에는 없는 것이 아쉬웠던 부분이기도 했습니다.
한 1년 전 즈음부터 은전한닢 프로젝트라는 이름으로 MeCab과 세종 코퍼스를 이용하여 한국어 형태소 분석기를 작성하신 분이 계십니다. 점차 활성화 되어 가는 상황을 지켜보면서 현재도 이 프로젝트를 많이 지지하고 있으며, 또한 참여하고 싶은 마음도 굴뚝같습니다. 그러나 한편으로는 일본어 분석을 위해 일본에서 개발된 코드에 한국어 분석을 맡기는 상황이 한국에서 자연어처리를 하는 한 개발자로써 자존심이 상하기도 했습니다.
결론적으로 말하자면 저는 자존심 때문에 바퀴를 새로 발명하려고 합니다. ^^; 업무 외 시간에 잉여롭게 진행하는 프로젝트라 사실 좀 자신이 없습니다. 그리고 솔직히 MeCab을 만든 슈퍼(?) 개발자보다 잘 만들 수 있을 지도 모르겠습니다. 그래도 이렇게 공개적으로 그것도 코드 한줄 없이 먼저 질러버려야 게으른 저를 채찍질할 수 있고, 삽을 뜰 수 있으리라 생각해서 이렇게 입개발로 먼저 시작을 합니다.