상세 컨텐츠

본문 제목

코퍼스 내에서 알려지지 않은 새로운 명사(미등록어)를 추출하기

그냥 공부

by ∫2tdt=t²+c 2018. 9. 2. 15:48

본문

한국어에 대한 자연언어처리를 실시할 때 맞닥뜨리는 가장 큰 문제는 새로운 단어를 나타나면 분석기가 얘를 파악하지 못하고 제멋대로 분해해버린다는 겁니다. 고유명사나 외래어가 많이 포함된 텍스트를 분석할때 이런 문제가 크게 두드러지는 데요, 예를 들면 다음과 같은 문제인거죠.


이민철, 김혜진. (2018). 텍스트 마이닝 기법을 적용한 뉴스 데이터에서의 사건 네트워크 구축. 지능정보연구, 24(1), 183-203. 에서 발췌

한국어 형태소 분석기를 계속 최신으로 업데이트하며 새로운 고유명사들을 추가해준다고 해도 새로운 단어가 만들어지는 속도를 따라잡을 수는 없기 때문에 이는 큰 문제입니다. 위의 예시를 가져온 연구에서는 형태소 분석 후 후처리로 PMI를 이용해 이 값들이 높은 형태소 쌍을 다시 묶어주는 방법으로 이 문제를 해결하고자 했습니다.


저런 문제로 계속 고민을 하던 중에 최근 https://github.com/lovit/soynlp 에서 간단한 통계적 방법으로 단어로 예상되는 문자열을 추출할 수 있는 기법을 소개하는 것을 보고, (이 기법이 굉장히 좋아보여서...) 이를 응용하는 실험을 해보았는데, 간단한 방법임에도 의외로 높은 성능을 보여서 본 포스팅에서 이렇게 소개하게 되었습니다. soynlp에서 제안하는 기법은 Cohesion Probability와 Branching Entropy라는 두 척도를 결합하는 것으로 설명할 수 있습니다. 각각이 어떤 개념인지 간략하개 소개해보겠습니다.


Cohesion Score

이는 한 무리의 문자열이 얼마나 밀접하게 뭉쳐서 등장하는지를 파악하는 척도입니다.  (https://lovit.github.io/nlp/2018/04/09/cohesion_ltokenizer/) a_1 a_2 a_3 ... a_n 형태의 문자열이 있을 경우 이 문자열의 Cohesion Score는 다음과 같이 계산된다고 합니다.

(이때 n(~~)는 코퍼스 내에서 문자열 ~~이 등장한 횟수입니다.)

예를 들어 '김'이 총 100번 등장하고, '김삿갓'이 25번 등장한다면 '김삿갓'의 Cohesion Score는 sqrt(25/100) = 0.5가 됩니다. 이 값은 [0, 1]범위에 있으며, 높을수록 전체 코퍼스 내에서 해당 문자열들은 뭉쳐서 등장할 가능성이 높다고 판단할 수 있습니다.

앞서 제시된 공식은 forward 방향으로 계산한 경우고, 이를 뒤집어서 backward 방향에서 Cohesion Score를 계산할 수도 있습니다. 이경우는

가 되겠지요.


Branching Entropy

이는 문자열의 경계에서 얼마나 다양한 다음 문자가 등장하는지를 계산하는 척도입니다. (Jin, Z., & Tanaka-Ishii, K. (2006, July). Unsupervised segmentation of Chinese text by use of branching entropy. In Proceedings of the COLING/ACL on Main conference poster sessions (pp. 428-435). Association for Computational Linguistics.

'슈'라는 글자 다음에 어떤 글자가 올지는 예측하지 어렵지만, '슈퍼'라는 글자 다음에는 어떤게 올지, '슈퍼컴'이라는 글자 다음에는 어떤게 올지 대충 감이 옵니다. 이는 맥락이 하나씩 늘어나면서 다음 글자를 점점 정확하게 예측할 수 있기 때문이죠. '슈퍼컴퓨터'라는 글자가 등장한 다음에는 다시 그 다음에 어떤 글자가 나타날지 예측하기 어렵습니다. 이는 이 지점이 단어의 경계이기 때문에 다시 다양한 글자가 등장할 수 있기 때문입니다. 따라서 단어 내부에서는 단어 뒷쪽으로 갈수록 Branching Entropy가 감소하는 경향을 보이다가, 한 단어가 끝나고 그 경계에 다다르면 다시 Branching Entropy는 증가한다는 것이죠. 따라서 이 값을 기준으로 단어의 경계를 판단하는 것이 가능합니다.

Branching Entropy는 Entropy라는 이름처럼 Entropy와 동일하게 계산됩니다.

(이때 w는 문자열, V는 모든 문자의 집합, n(w)는 코퍼스 내 w의 등장빈도, n(wc)는 w 뒤에 c가 등장하는 빈도)

이 역시 Cohesion Score마냥 거꾸로 계산될 수도 있습니다.


추출 결과를 보자!

실제 단어 후보 추출에는 forward Cohesion Score, backward Cohesion Score, forward Branching Entropy, backward Branching Entropy를 모두 곱한 값을 사용할 겁니다. 그래야 앞으로 봐도 뒤로 봐도 뭉쳐서 등장하며, 단어의 경계가 확실한 녀석이 높을 값을 가질테니까요.

이 아이디어를 바탕으로 대규모 텍스트를 멀티스레드로 처리할 수 있도록 c++ 기반의 단어추출기를 작성하였습니다. 본 포스팅의 실험 결과들은 이 프로그램으로 생성되었구요, 이 코드는 곧 한국어 형태소분석기 Kiwi 0.5 버전에 포함될 예정입니다. 깔끔하면서도 사용하기 간편한 코드를 찾으시는 분은 https://github.com/lovit/soynlp 에 잘 구현된 파이썬 코드가 있으니 이를 참조하시면 될듯합니다.


새롭고 다양한 단어들이 많이 등장하는 문헌이 어디있을까 생각하다 나무위키 내 문서 100만개를 가지고 실험을 돌려보았습니다. 다음은 위에서 제시한 score를 내림차순으로 정렬한 결과입니다. (최대 길이는 10자로 제한)


형태 단어 점수 빈도
(...) 2.64514 20597
펭귄 2.60586 185
앨범 2.31919 15882
[] 2.29112 364
^^ 2.28099 1644
다. 2.1136 886179
으로 2.10977 297689
덥스텝 1.93202 360
프로그램 1.79763 7775
캡슐 1.73033 1225
애니메이션 1.71737 5010
처럼 1.71206 16725
^^음력^^(15 1.59744 49
에서 1.59012 313923
|| 1.58766 28809
__ 1.52954 1897
묀헨글라트바흐 1.52708 38
괜찮 1.43334 2998
훨씬 1.41296 4163
엔터테인먼트 1.40492 1234
ㅠㅠ 1.29671 42
^^음력^^(~1 1.29571 75
되었다. 1.29163 58400
챔피언 1.25429 5008
^^2^^ 1.25282 178
http:// 1.24979 1312
VOCALOID 1.19313 1057
훌륭 1.16722 1389
컨트롤 1.13331 2782
뚜껑 1.10178 407


생각보다 지저분한 결과가 나오는것 같습니다. 일단 10자 이상의 영단어들이 잘려서 등장하는 경우나 특수문자가 섞인 경우가 많으므로 이를 필터링하면 다음과 같습니다.


형태 단어 점수 빈도
펭귄 2.60586 185
앨범 2.31919 15882
다. 2.1136 886179
으로 2.10977 297689
덥스텝 1.93202 360
프로그램 1.79763 7775
캡슐 1.73033 1225
애니메이션 1.71737 5010
처럼 1.71206 16725
에서 1.59012 313923
묀헨글라트바흐 1.52708 38
괜찮 1.43334 2998
훨씬 1.41296 4163
엔터테인먼트 1.40492 1234
ㅠㅠ 1.29671 42
되었다. 1.29163 58400
챔피언 1.25429 5008
훌륭 1.16722 1389
컨트롤 1.13331 2782
뚜껑 1.10178 407
캐릭터 1.08462 14493
했다. 1.03992 69781
올림픽 1.00863 4543
패턴 0.996877 19371
챔피언십 0.976197 774
빰빰빰 0.962082 40


이제 좀 단어처럼 보이는 것들이 나타나는 것을 볼 수 있습니다. 그러나 으로, 처럼, 에서, 괜찮, 되었다. 마냥 같이 붙어다니는 글자이긴 하지만 우리가 원하는 새로운 명사가 아닌 조사가 붙은 경우, 동사에 어미가 붙은 경우도 왕왕 뽑힌다는 문제가 있습니다. 우리가 발견하고자하는 새로운 단어들은 대게 명사일 것이기 때문에 명사만 추출하도록 척도 하나를 더 추가하는게 좋을듯합니다.


명사 점수를 제안해보자

(https://lovit.github.io/nlp/2018/05/08/noun_extraction_ver2/ 에는 명사 여부를 판별하기 위해 L-R 그래프를 사용하는 방법을 소개하고 있습니다. 여기에서는 그 포스팅과는 다르게 단순히 확률만을 바탕으로 명사 여부를 판단하는 가중치를 제안합니다.)


다행히도 한국어의 명사들은 명백한 분포적인 특성을 가집니다. 조사가 뒤에 붙는다는 것인데요, 이 특징 덕분에 한국인들은 처음 보는 단어가 나타나도 당황하지 않고 그 맥락을 통해 명사인지 아닌지 예상하곤 합니다.

꿕덕오가 소리를 지르고 있다.

너 어제 꿕덕오랑 같이 있던데?

하루에 세 번 꿕덕오를 본다.

꿕덕오가 뭔지 아무도 몰라도, -가, -랑, -를 처럼 조사와 같이 나타나므로 어떤 잘 모르는 존재에 대한 명칭이라는 걸 명확히 알 수 있습니다. 따라서 그 뒤에 무엇이 나올지를 보면 그 단어가 명사처럼 쓰이는지 아닌지를 알 수 있는거죠.


이를 바탕으로 어떤 문자열이 명사일 가능성을 다음과 같이 추정해볼 수 있겠습니다. 먼저 다음과 같이 조건부확률을 생각해봅시다.


: 어떤 단어가 명사일때 그 명사 뒤에 글자 s_i가 등장할 확률

: 어떤 단어가 명사가 아닐때 그 명사 뒤에 글자 s_i가 등장할 확률


그리고 잘 모르는 단어 w뒤에 s_i가 등장한 횟수를 n_i라고 하면

과 같이 계산할 수 있을 겁니다. 따라서 주어진 맥락 s에서 w가 명사일 확률과 명사가 아닐 확률을 비교하면 이 녀석이 얼마나 명사일 가능성이 높은지 알 수 있을 겁니다. 계산의 편의를 위해 로그를 취한 값을 명사 점수로 쓰도록 합시다. 따라서 명사 점수가 0인 경우 명사일 확률과 명사가 아닌 확률이 동일한것이고, 양수면 명사일 확률이 더 높고, 음수면 명사가 아닐 확률이 더 높은것이 됩니다.

(n은 단어 w의 등장횟수입니다. 1/n를 곱해준 이유는 많이 등장하는 단어일수록 점수가 커지는것을 막기 위함입니다.)


이 계산식은 의외로 간단하게 계산이 됩니다. 베이즈의 정리에 따르면 P(W|S) = P(S|W)*P(W)/P(S)이므로

가 됩니다. (n(w_noun s_i)는 명사인 단어 뒤에서 s_i가 등장한 빈도, n(w_notnoun s_i)는 명사가 아닌 단어 뒤에서 s_i가 등장한 빈도입니다. 간혹 둘 중 하나 이상이 0일 경우가 있는데 이경우 아주 작은 양수 epsilon으로 대체하여 W(s_i)값이 무한대로 발산하는 것을 막을 수 있습니다.)


이 값을 W(s_i)라고 정의하면 최종적으로 명사점수는 다음과 같이 간단하게 계산이 되는거죠!


그럼 실제 데이터에서 W(s_i)들을 뽑아보도록 하죠. 세종 말뭉치에서 명사인 단어 뒤에 오는 글자와 명사가 아닌 단어 뒤에 오는 글자의 빈도를 파악해 W(s_i)값을 계산해보았습니다.


받침없음 받침있음
s W(s) s W(s)
5.768 9.746
5.113 9.062
4.017 8.140
3.879 7.764
3.088 7.193
2.833 6.791
2.796 6.768
2.755 6.733
2.651 6.522
2.526 6.413
-8.451 -8.805
-9.007 -9.034
-9.510 -9.277
-9.830 -9.956
-10.034 -10.346
-10.221 -10.418
-10.319 -10.448
-10.572 -11.574
-11.045 -12.979
-11.904 -13.651


명사의 경우 받침으로 끝나면 -이, -을, 받침이 없으면 -가, -를이 붙는것처럼 뒤에 따라오는 단어의 형태가 다르므로, 받침이 있는 경우와 없는 경우를 나누어 통계를 냈습니다. 점수를 살펴보니 의외의 것들이 많습니다. 당연히 명사 뒤에 조사가 붙는 경우가 많으므로 조사의 점수가 높게 나올줄 알았는데, -이, -를, -은 과 같은 글자들은 조사말고도 어미 등 다양한 단어로 쓰이는 경우가 많아 그 점수가 비교적 낮은 1~2점대에 분포합니다. 반면 명사에만 확실히 붙는 접미사들(-샷, -꾼, -적, -님) 같은 단어들이 점수 상위권에 분포했고, 반대로 명사 뒤에는 절대로 붙지 않는 -았, -었, -겠 등이 하위권에 분포했습니다.


위와 같은 나무위키 데이터에서 뽑은 단어 후보들을 명사 점수로 내림차순 정렬해보았습니다.


형태 단어 점수 빈도 명사 점수
때문 0.281474 61991 0.726225
방송통신심의위원회 0.361982 31 0.70266
묀헨글라드바흐 0.914201 15 0.639183
마이크로시스템즈 0.250343 17 0.574044
꿋꿋 0.347839 88 0.333109
업그레이드 0.887474 4317 0.329136
옴스크트란스마쉬 0.650359 16 0.075041
길가메쉬 0.3179 396 0.024884
옐로우스타 0.301828 50 -0.05544
방송통신위원회 0.300461 58 -0.05638
슈바인슈타이거 0.453663 97 -0.08298
퀄리티 0.712102 2148 -0.14951
애니매트로닉스 0.371522 174 -0.15054
뻣뻣 0.482556 27 -0.24376
뽁뽁 0.553278 25 -0.24766
묀헨글라트바흐 1.52708 38 -0.25093
뚜렷 0.448545 356 -0.2723
노동조합연맹 0.268317 20 -0.28705
블레이즈 0.306747 1119 -0.31394
찝찝 0.309174 102 -0.33772
질병통제예방센터 0.286054 17 -0.36583
톱니바퀴 0.519932 125 -0.37221
빽빽 0.347489 102 -0.38033
멜로디 0.492532 1664 -0.38863


확실히 명사다운 녀석들만 뽑혔네요. 정확히 명사 점수를 몇 이상으로 잘라내는게 성능상 유리할까요? 실험을 통해서 확인해봤습니다.


최적의 명사 점수 기준을 찾자

나무위키 100만개 문헌(이하 데이터A) 및 세종 말뭉치의 학술적 글(약 15만문장 이하 데이터 B), 수필(약 3200문장, 이하 데이터 C), 에 대해서 단어점수 0.25 이상의 후보를 전부 뽑고, 명사 여부를 판별하여 명사 점수에 따른 정확률/재현율/F값을 계산했습니다.


<데이터A의 결과, N=934, R=319>

명사점수 필터안함 -5 -4 -3 -2.5 -2 -1 0
정확률 34% 34% 52% 73% 79% 83% 89% 78%
재현율 100% 100% 98% 86% 71% 53% 20% 2%
F값 51% 51% 68% 79% 75% 65% 33% 4%


<데이터B의 결과, N=480, R=284>

명사점수 필터안함 -5 -4 -3 -2.5 -2 -1 0
정확률 59% 59% 73% 87% 91% 95% 97% 97%
재현율 100% 100% 97% 90% 78% 68% 34% 11%
F값 74% 74% 84% 89% 84% 79% 51% 19%


<데이터C의 결과, N=215, R=92>

명사점수 필터안함 -5 -4 -3 -2.5 -2 -1 0
정확률 43% 43% 67% 94% 97% 100% 100% 100%
재현율 100% 100% 97% 89% 80% 71% 45% 17%
F값 60% 60% 79% 92% 88% 83% 62% 30%


세 데이터 셋 모두 명사 점수가 -3이상인 경우로 결과를 필터링 했을때 제일 높은 F값을 보였습니다. 실제 명사 추출 결과는 다음과 같구요.

<데이터A에서의 추출 결과 상위 30개>

추출 명사 단어 점수 빈도 명사 점수
펭귄 2.60586 185 -1.87031
앨범 2.31919 15882 -2.30981
덥스텝 1.93202 360 -2.83123
프로그램 1.79763 7775 -2.16874
캡슐 1.73033 1225 -2.10403
애니메이션 1.71737 5010 -2.48734
묀헨글라트바흐 1.52708 38 -0.25093
엔터테인먼트 1.40492 1234 -0.97811
챔피언 1.25429 5008 -2.50287
훌륭 1.16722 1389 -0.4754
캐릭터 1.08462 14493 -0.90806
올림픽 1.00863 4543 -2.377
패턴 0.996877 19371 -1.91729
챔피언십 0.976197 774 -2.58069
빰빰빰 0.962082 40 -1.6554
뽈뽈 0.96018 13 -2.76622
묀헨글라드바흐 0.914201 15 0.639183
방송 0.902403 16998 -1.59294
ㅎㄷㄷ 0.893828 47 -2.77496
업그레이드 0.887474 4317 0.329136
짬뽕 0.853513 111 -1.64744
시리즈 0.839681 25842 -1.29864
샌프란시스코 0.838099 224 -1.61327
컨셉 0.822354 2861 -2.35461
싱글 0.8006 9173 -2.65445
쩝쩝 0.783081 12 -0.43271
떡밥 0.759116 1377 -1.93033
몬스터 0.750415 6590 -0.98342
컨페더레이션스컵 0.746608 63 -1.66615
뮤직비디오 0.737474 1398 -0.93535


<데이터B에서의 추출 결과 상위 30개>

추출 명사 단어 점수 빈도 명사 점수
프로그램 2.12252 355 -1.9839
커뮤니케이션 1.94275 98 -2.09273
菩薩 1.79017 7 -2.83218
벤처캐피털 1.75066 230 -1.98349
메커니즘 1.36887 179 -1.60293
블라디보스똑 1.21645 45 -2.96748
벤처기업 1.05608 624 -0.9949
텍스트 1.0543 233 -0.44709
컴퓨터 1.00495 473 -0.96104
넝쿨 0.954456 12 -1.09689
{삼국사기} 0.934437 45 -0.26598
훌륭 0.925882 333 -0.77559
네덜란드 0.898441 411 -1.67364
뚜렷 0.883939 226 -0.17735
엥겔 0.870202 23 0.110289
옴부즈맨 0.862304 32 -1.63026
슈퍼마켓 0.836842 22 -1.66766
저널리즘 0.834388 334 -1.08889
헝겊 0.801202 12 -2.9038
벤조다이아제핀 0.800029 13 -1.7186
思想 0.795563 24 -2.04377
[태백산맥] 0.792681 33 -1.67742
벤치마킹 0.789307 48 -2.16642
퍼센트 0.78532 265 -0.49712
언론 0.753156 3098 -0.87213
텔레비전 0.73804 438 -2.38495
사회 0.724102 9967 0.257968
텔레커뮤니케이션 0.713939 6 -1.48555
톨릭 0.711409 129 -2.86353
녹색마케팅 0.692619 32 -0.99195


<데이터C에서의 추출 결과 상위 30개>

추출 명사 단어 점수 빈도 명사 점수
디지털 2.85097 80 -1.43262
보험 2.43206 537 -1.04803
마케팅 2.03848 79 -1.9164
교육 1.82377 78 -2.54779
영업 1.79619 558 -0.67341
골프 1.56742 54 -0.18321
영업사원 1.45125 196 -0.98512
엘리베이터 1.3838 8 -0.28559
핸드폰 1.09845 5 -1.40489
패러다임 1.05324 27 -2.82567
고객 0.98746 745 -1.09519
생명보험 0.926805 81 -1.06023
영업소 0.91635 188 -0.3004
설계사 0.886709 172 0.199133
사람 0.853398 590 -1.24054
영업소장 0.832817 98 -2.06282
계약 0.81727 187 -2.86966
훌륭 0.775452 31 -0.68614
필요 0.769048 130 0.427772
영업관리자 0.759827 41 0.184933
규칙 0.74332 15 -2.24724
컴퓨터 0.739657 10 -0.01264
경쟁 0.73868 84 -1.0606
설계사들 0.709774 47 -1.05045
생각 0.700458 166 -0.64285
타금융권 0.688851 14 -0.06588
보험회사 0.686198 117 0.078695
칭찬 0.670939 16 -1.34844
생명보험회사 0.662783 24 0.069338
디지털마케팅 0.641333 5 -1.21936


엄밀하게 말하면, 필요, 훌륭과 같은 단어는 명사(NNG)보다는 '-하다'가 붙어서 쓰이는 어근(XR)이 맞습니다만 본 실험에서는 모두 명사라고 간주하였습니다. 잘못 뽑힌 결과는 취소선으로 표시하였는데, 매우 드물었습니다. 실제 추출 결과 중 절반 정도는 이미 형태소 분석기 사전에 포함되어 있으나, 나머지 절반은 그렇지 않은 것으로 확인되었고, 형태소 분석에 앞서 이렇게 얻은 명사 목록을 사전에 추가하여 형태소 분석을 진행할 경우 단어가 잘못되게 분할되는 것을 막을 수 있을 것으로 예상됩니다.


마치며

새로 만들어지는 단어들 중 많은 수가 명사인건 사실이지만, 부사나 의성어/의태어, 간혹 동사 등이 새로 만들어지기도 합니다. 일단 현재는 명사에 관해서만 다루었는데 나머지의 경우를 어떻게 탐지할 수 있을지 고민해볼 필요가 있겠습니다. 전 당분간 고민은 멈추고 새로운 명사를 탐지하는 기능을 갖춘 Kiwi 0.5를 개발하는데 집중하려고 합니다~


+ Kiwi 0.5부터 이 포스팅에서 설명한 미등록어 자동 추출 기능이 추가되었습니다. 파이썬 래퍼인 Kiwipiepy 최신 버전을 설치하시면 다음과 같이 간편하게 사용할 수 있습니다.




관련글 더보기

댓글 영역