soyspacing. Heuristic Korean Space Correction, A safer space corrector.

한국어 띄어쓰기 오류는 Conditional Random Field (CRF) 와 같은 sequential labeling algorithm 을 이용하여 교정할 수 있습니다. 사람들은 보통 여러 어절을 붙여씁니다. 띄어쓰기 오류는 ‘띄어써야 할 부분을 붙여쓰는 경우’ 입니다. 그런데 CRF 는 공격적으로 띄어쓰기를 교정하는 경향이 있습니다. 데이터 분석에서의 띄어쓰기 교정의 목적은 붙어있는 여러 어절을 분리하여 이후의 자연어처리 분석과정의 정확성을 높이는 것입니다. 공격적인 띄어쓰기는 이후의 분석을 더 어렵게 만듭니다. 이번 포스트에서는 안전지향적인 띄어쓰기 교정기를 소개합니다. 머신 러닝 알고리즘인 CRF 처럼 좌/우의 등장하는 글자를 features 로 이용하는 heuristic algorithm 입니다. 또한 데이터에 띄어쓰기 오류가 어느 정도 있어도 학습용으로 이용할 수 있음을 실험으로 확인합니다.

한국어 띄어쓰기 교정 문제

Conditional Random Field 의 단점을 보완하여 안전지향적인 한국어 띄어쓰기 교정기를 소개합니다. 이 작업은 Scatter Lab 과 함께 하였습니다. 한국어의 띄어쓰기 교정 문제에 대한 정의부터 시작합니다. 먼저 아래 문장을 읽어보세요.

'데이 터를만 지는직 업'

위 문장은 읽기 불편합니다. 띄우지 않아야 할 글자, 즉 어절의 중간을 띄우면 사람도 읽기가 어렵습니다. 하지만 아래 문장은 읽을 수 있습니다.

'데이터를 만지는직업'

‘만지는직업’이라는 두 어절은 하나의 의미 단위가 될 수 있습니다. Chunking 기준으로 띄어쓰기를 하지 않아도 익숙한 단어들은 눈에 쏙 들어옵니다. 이미 익숙한 문장이기 때문에 아래처럼 세 어절을 모두 붙여도 사람은 읽을 수 있습니다.

'데이터를만지는직업'

이처럼 한국어의 띄어쓰기 오류는 띄어써야 하는 어절 사이를 붙여쓰는 경우에 발생합니다. 읽는데 불편함이 없기 때문에 발생하는 오류입니다. 하지만 붙여써야 하는 부분을 띄어쓰면 사람도 읽기가 어렵습니다.

우리가 풀어야 하는 띄어쓰기 교정 문제는 ‘본래 띄어야 하는데 붙여쓴 부분을 다시 띄어주는 것’입니다.

띄어쓰기용 학습데이터 선택

이전 crf 를 이용한 한국어 띄어쓰기 교정 포스트처럼 영화 ‘라라랜드’의 영화평 데이터를 이용하여 띄어쓰기 교정기를 만듭니다. 우리가 연습으로 사용할 데이터는 여기에 올려두었습니다.

띄어쓰기 교정기는 띄어쓰기가 잘 지켜진 데이터의 패턴을 학습한 뒤, 띄어쓰기가 잘 되지 않은 문장을 교정합니다. 이를 위한 학습데이터를 준비할 때에는 두 가지를 신경써야 합니다.

첫째는 학습데이터의 vocabulary distribution 입니다. 학습데이터를 이용하는 supervised algorithms 은 학습데이터로 가르쳐 준 일은 잘 수행하지만, 가르쳐주지 않은 일을 잘할거라고는 보장하기 어렵습니다. 띄어쓰기 교정기가 이용하는 features 는 앞/뒤에 등장한 글자입니다. 학습데이터에 등장한 어절 간의 간격 패턴을 학습하여 띄어쓰기 오류를 교정합니다. 하지만 처음 보는 단어들이 등장한다면 모델은 혼란스럽습니다. 그렇기 때문에 첫째가 vocabulary distribution 이 비슷한 학습데이터 입니다. 적어도 그럴 데이터를 우리는 가지고 있습니다. 바로 띄어쓰기를 교정할 데이터입니다.

교정할 데이터에는 띄어쓰기 오류가 포함되어 있습니다. 그러나 데이터의 모든 문장이 띄어쓰기 오류가 있을리는 없습니다. 그건 오류가 아니라 ‘언어 패턴’입니다. 오류란, 같은 조건에서 소수로 발생하는 패턴입니다. 한국어의 어절의 평균 길이는 약 4 음절입니다. 학습데이터의 문장들 중에서 띄어쓰기가 그나마 잘 지켜진, 평균 어절 길이기 짧은 문장들을 선택하여 학습데이터를 만듭니다. 이 과정에 일부 노이즈가 섞여도 괜찮습니다. 오류와 정답이 1:9 비율로 존재한다면 모델이 알아서 정답의 편을 들것입니다.

즉 한국어 띄어쓰기 교정은 앞/뒤에 등장하는 단어를 고려할 때 주로 띄어쓰는 부분을 붙여썼다면 이를 띄어주는 과정입니다.

Idea of proposed algorithm

'이런문장을띄어쓰기한다고생각해보자'

우리는 위의 문장을 어떻게 띄어쓸까요? 대부분 아래처럼 띄어쓸 것입니다.

'이런 문장을 띄어쓰기 한다고 생각 해 보자'
'이런 문장을 띄어쓰기 한다고 생각해 보자'

이번에는 앞에서부터 위 문장을 읽지 말고 뒷부분의 ‘다고생각해보’ 만을 바라보세요. 조금 부자연스럽지만 ‘다고 생각해보’라고 띄울 수 있습니다. 한 번 더, 조금 더 넓게 ‘한다고생각해보자’ 을 바라보면, 더 큰 확신을 가지고서 ‘한다고 생각해보자’라고 [-다고] 다음을 띄울 것입니다.

이 말의 의미는 한 부분을 띄울지 판단하기 위해서는 맥락을 파악할 정도만의 local 정보로도 충분하다는 의미이며, 그 local 정보가 확실해질수록 띄어쓰기를 자신감있게 할 수 있습니다.

위 관찰로부터 우리는 features 를 설계합니다. 이는 CRF 가 이용하는 features 와도 같습니다. Features 의 위치에 따라 그 종류를 세 가지 (L, C, R) 로 나눴을 뿐입니다.

Left-side features (L)

c[-2:0]: 본인 글자 c[0]와 앞의 2개 글자
c[-3:0]: 본인 글자 c[0]와 앞의 3개 글자
...
c[-6:0]: 본인 글자 c[0]와 앞의 6개 글자

Central features (C)

c[-1:1]: 앞 뒤 1개 글자
c[-2:1]: 앞 2글자, 뒤 1글자
c[-1:2]: 앞 1글자, 뒤 2글자
...
c[-1:5]: 앞 1글자, 뒤 5글자

Right-side features (R)

c[0:2] : 본인 글자 c[0]와 뒤의 3개 글자
...
c[0:6] : 본인 글자 c[0]와 뒤의 6개 글자

만약 아래와 같이 각 에 대해서 띄어쓰기가 되어있다면 ‘한다고’라는 글자 다음에 띄어써야 할까요? 우리의 데이터에서 ‘한다고’ 다음에 80 번이 띄어썼고, 3 번 븥여 썼습니다. 이로부터 띄어쓸 점수를 점 으로 생각할 수 있습니다.

이처럼 띄어쓴 횟수는 양수로, 붙여쓴 횟수를 음수로 계산하면 띄어쓰기 점수는 사이로 bounding 됩니다. 점이면 반드시 띄어쓰고, 점이면 반드시 붙여쓴다는 의미입니다. 만약 점수가 0 점 근처라면 손대지 않는 것이 좋습니다. 띄어쓰기 교정기가 잘못내린 결정은 이후에 이용할 자연어처리기들이 떠안아야 하기 때문입니다.

c[-2:0]: 한다고,  000   : 3번
c[-2:0]: 한다고,  001   : 80번
c[-3:0]: 기한다고, 1001  :15번
c[-3:0]: 기한다고, 0000  : 3번

띄어쓰기를 판단할 글자의 왼쪽이나 오른쪽 부분만을 살펴보면 잘못된 판단을 할 수 있습니다.

'C[-3:0] = 지금나가'

‘-가’ 다음에 띄어쓸 가능성이 매우 높아 보입니다만, 오른쪽의 한 글자를 더 보니 ‘C[-3:1] = 지금나가요’ 였습니다. ‘C[-3:0] = 지금나가’ 라는 feature 는 어떤 문맥에서의 ‘C[-3:0] = 지금나가’ 인지를 살펴봐야 합니다. 그렇기 때문에 central features 가 가장 적합합니다. 앞, 뒤의 문맥을 모두 보기 때문입니다.

만약 L, C, R 세 개의 features 의 띄어쓰기 점수가 라면 모든 경우에 띄어도 된다고 말하니 안심하고 띄어쓰기를 해도 됩니다.

그런데 infrequent features 는 혼란을 줄 수 있습니다. L 이 3 번 띄고 0 번 안띈 경우가 하였다면 점수는 점 입니다. Infrequent features 는 노이즈에 민감할 수 있기 때문에 최소 번 (parameter: min_count) 나온 경우에만 features 로 인정합니다. L, C, R features 의 빈도수가 min_count 이하라면 띄어쓰기 각 시점의 띄어쓰기 점수는 0 점으로 정의합니다.

분명 C 가 가장 좋은 features 라 하였습니다. 그럼에도 L 과 R 을 이용해야만 합니다. C 를 이용하여 본인 글자 c[0] 다음을 띈다면, 이는 C 가 c[0] 을 기준으로 두 어절이 등장한 문맥이라는 의미입니다. 두 어절이 반드시 함께 등장해야만 C 가 만들어집니다. 그렇지 않은 경우들이 많으니 L 과 R 을 이용합니다. C 의 띄어쓰기 점수가 0 이더라도 L 과 R 이 의 점수를 보인다면 우리는 띄어쓰기를 할 수 있습니다. 앞의 어절에서도 이 부분은 어절의 끝이라 말해주고, 뒤의 어절에서도 이 부분은 어절의 시작이라고 말해주는 것입니다.

이 상황을 heuristic rules 로 표현합니다. Rule based classification 입니다. 아래에 등장하는 threshold 들은 모두 모델의 패러매터 입니다.

띄어쓰기 판단 규칙

  1. 세 종류의 L, C, R의 빈도수가 0보다 크고, C[0] 부분에 대하여 동일한 label을 보이면 L, C, R 점수의 평균을 띄어쓰기 점수로 이용
  2. L, R 의 빈도수가 0 이더라도 C 의 빈도수가 0 이상이면 C 의 점수를 띄어쓰기 점수로 이용 (L이나 R의 빈도수가 0보다 크면 이와 C의 평균 점수를 띄어쓰기 점수로 이용)
  3. C 의 빈도수가 0 일 때, L 과 R 의 빈도수가 0 보다 크고 동일한 label을 보이면 L과 R의 평균 점수를 띄어쓰기 점수로 이용
  4. 그 외, L이나 R이 단독으로만 빈도수가 0 이상이면 띄어쓰기 점수를 0으로 설정. (단, 첫글자는 제외)

그럼 길이가 인 문장의 어느 부분부터 띄어쓰기를 교정해야 할까요? Sequential labeling 알고리즘들은 전방향 혹은 역방향의 순차적인 labeling 을 수행합니다. 한 방향으로 classification 을 하다보면 잘못된 classification 을 할 수도 있습니다. 이 때의 error 는 고치지 못하고 다음 후보가 그 영향을 받습니다. Error propagation 이 일어납니다.

그러지말고 우리가 확실히 알고 있는 부분부터 띄어쓰기를 수행합니다. 확신을 가지는 부분부터 labeling 을 수행하면 error propagation 의 가능성이 줄어듭니다.

위 예제에서 ‘c[-3:0] = 기한다고’를 판단하기 전에 ‘c[-3:2] = 띄어쓰기한다’에 대하여 ‘띄어쓰기 한다’로 띄어쓴다고 판단을 하였다면, ‘c[-3:0] = 기한다고’를 판단할 때는 이미 ‘기 한다고’로 띄어져 있습니다. 확실한 부분이 먼저 띄어쓰기 결과를 말해주면 주변의 애매모호한 부분에 좀 더 확실해집니다.

적어도 우리는 (‘c[-3:0] = 기한다고’, tag[-3:0] = ‘0000’) 을 이용하지 않아도 됩니다. c[-3:0] 일 때 c[0] 다음에 띄어쓸 점수가 에서 으로 변합니다. 확실한 부분을 먼저 tagging 하면 주위의 애매모호한 부분이 더 확실해집니다. 확실함이 propagation 됩니다.

그러나 이 방법은 길이가 인 문장에 대하여 번의 loop 를 돌아야 함을 의미합니다. 문장이 길어질수록 띄어쓰기 오류 수정 시간이 길어집니다. 하지만 띄어쓰기 점수의 절대값이 0.9 정도가 된다면 다음 round 로 갈 것도 없이 태깅을 해도 됩니다. 이 부분은 force_abs_threshold 로 구현되어 있습니다. 띄어쓰기 점수의 절대값이 force_abs_threshold 보다 크면 이번 round 에 태깅을 합니다. 이 부분을 force tagging 이라 명합니다.

나머지에 대해서는 띄어쓰기 점수 절대값이 가장 큰 순서대로 태깅을 시작합니다. nonspace_threshold 보다 작거나 space_threshold 보다 큰 글자에 대하여 각각 붙여쓰기, 띄어쓰기를 합니다. 점수가 nonspace_threshold 보다는 큰데, space_threshold 보다 작다면 애매모호한 경우이니 손대지 맙시다. 띄어쓰기는 애매모호하면 건들지 않는게 최선입니다. 이 부분을 sequential tagging 이라 명합니다.

한 가지 더! 입력되는 문장 자체에 띄어쓰기가 되어 있는 경우가 있습니다. 이는 사람(님)이 달아주신 레이블입니다. 이는 확신을 가지고 띄어쓰기 태그로 이용합니다.

Software: soyspacing

soyspacing 은 위 아이디어를 구현한 Python package 입니다. 설치는 pip install 이 가능합니다. 튜토리얼을 작성하는 현재의 버전은 soyspacing==1.0.13 입니다.

 pip install soyspacing

모델은 text 파일로부터 띄어쓰기 패턴을 학습합니다. 학습 과정은 (L, C, R) features counting 입니다.

from soyspacing.countbase import CountSpace

corpus_fname = 'YOURS'
model = CountSpace()
model.train(corpus_fname)

모델의 save, load 는 아래와 같습니다. 데이터를 저장할 때 json_format 으로 저장하면 {‘한다고’ :{‘000’: 50, ‘001’: 25}, .. } 와 같은 형식으로 substring 과 space tag 정보가 저장됩니다. json_format=False 이면 list 형식으로 저장됩니다. 그렇기 때문에 json_format=True 를 이용하실 때에는 모델 이름의 맨 뒤에 .json 을 붙여주세요.

model_fname = 'YOURS'
model.save_model(model_fname, json_format=False)
# model.save_model(model_fname, json_format=True)

model2 = CountSpace()
model2.load_model(model_fname, json_format=False)
# model2.load_model(model_fname, json_format=True)

띄어쓰기를 위한 parametes 는 다음처럼 입력할 수 있습니다. 네 가지 threshold parameters 의 의미는 위 챕터에 있습니다.

verbose=False
mc = 10  # min_count
ft = 0.3 # force_abs_threshold
nt =-0.3 # nonspace_threshold
st = 0.3 # space_threshold

sent = '이건진짜좋은영화 라라랜드진짜좋은영화'

sent_corrected, tags = model.correct(
    doc=sent,
    verbose=verbose,
    force_abs_threshold=ft,
    nonspace_threshold=nt,
    space_threshold=st,
    min_count=mc)

print('before: %s' % sent)
print('after : %s' % sent_corrected)

# before: 이건진짜좋은영화 라라랜드진짜좋은영화
# after : 이건 진짜 좋은 영화 라라랜드진짜 좋은 영화

correct() 는 두 가지 정보를 return 합니다. sent_corrected 는 띄어쓰기가 교정된 문장이며, tags 는 해당 문장의 띄어쓰기 태그입니다.

태그는 0, 1, None 으로 구성됩니다. 0 은 붙여쓰기, 1 은 띄어쓰기를 의미하며, None 은 min count 혹은 nonspace / space threshold 사이의 space score 여서 판단을 유보한 경우입니다. 이때에는 sent_corrected 에서도 붙어 있습니다. 위 예제에서는 ‘라진’ (라라랜드진짜) 부분의 띄어쓰기의 판단을 유보한 것입니다.

print(tags)
# [0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, None, None, 1, 0, 1, 0, 1]

한국어 평균 어절의 길이가 4 정도 된다는 의미는 띄어쓰기 태그와 붙여쓰기 태그의 비율이 4:1 이라는 의미이기도 합니다. Class imbalance 상황이 발생할 수도 있습니다 (물론 실제로는 그럼에도 불구하고 큰 영향이 없습니다). 띄어쓰기 태그의 횟수에 배수를 취하고 싶다면 space_importancy 를 조절합니다. Default 는 1 로 되어 있습니다. 2 로 설정하면 띄어쓴 태그의 횟수에 2 를 곱하여 (L, C, R) 점수를 계산합니다.

sent_corrected, tags = model.correct(
    doc=sent,
    space_importancy=1)

verbose=True 로 설정하면 verbose mode 입니다. Input 과 tagging 과정이 출력됩니다.

sent_corrected, tags = model.correct(
    doc=sent,
    verbose=True)

# Printing states
# ------------------------------------------
# Input: ? ? ? ? ? ? ? 1 ? ? ? ? ? ? ? ? ? 1
# 이건진짜좋은영화 라라랜드진짜좋은영화
# Ruled: ? ? ? ? ? ? ? 1 ? ? ? ? ? ? ? ? ? 1
# 이건진짜좋은영화 라라랜드진짜좋은영화
# Force tagged (iter=1): 0 1 0 1 0 1 0 1 0 0 0 ? ? 1 0 1 0 1
# Force tagged (iter=2): 0 1 0 1 0 1 0 1 0 0 0 ? ? 1 0 1 0 1
# ... 

debug=True 로 설정하면 debug mode 입니다. Debug mode 에서는 초기의 (L, C, R) score 와 feature count, 그리고 verbose mode 처럼 tagging 과정이 출력됩니다.

sent_corrected, tags = model.correct(
    doc=sent,
    debug=True)
0: 이 (-1.000, 32)	lcr = (0.000, 0.000, -1.000)
1: 건 (0.750, 48)	lcr = (0.000, 0.750, 0.750)
2: 진 (-1.000, 106)	lcr = (-1.000, -1.000, -1.000)
3: 짜 (0.703, 90)	lcr = (0.750, 0.655, 0.000)
4: 좋 (-1.000, 942)	lcr = (-1.000, 0.000, -1.000)
5: 은 (0.504, 1430)	lcr = (0.000, 0.403, 0.604)
6: 영 (-1.000, 1586)	lcr = (-1.000, -1.000, -1.000)
7: 화 (0.000, 0)	lcr = (0.000, 0.000, 0.000)
8: 라 (-0.998, 2030)	lcr = (-1.000, -1.000, -0.995)
9: 라 (-0.938, 2860)	lcr = (-0.882, -0.962, -0.968)
10: 랜 (-1.000, 2792)	lcr = (-1.000, -1.000, 0.000)
11: 드 (-0.211, 1840)	lcr = (-0.211, 0.000, 0.000)
12: 진 (-1.000, 58)	lcr = (0.000, 0.000, -1.000)
13: 짜 (0.655, 58)	lcr = (0.000, 0.655, 0.000)
14: 좋 (-1.000, 942)	lcr = (-1.000, 0.000, -1.000)
15: 은 (0.502, 1426)	lcr = (0.000, 0.403, 0.601)
16: 영 (-1.000, 1426)	lcr = (-1.000, -1.000, 0.000)
17: 화 (0.000, 0)	lcr = (0.000, 0.000, 0.000)
force tagging i=0, score=-1.000
force tagging i=1, score=0.750
force tagging i=2, score=-1.000
force tagging i=3, score=0.685
force tagging i=4, score=-1.000
force tagging i=5, score=0.504
force tagging i=6, score=-1.000
force tagging i=8, score=-0.998
force tagging i=9, score=-0.939
force tagging i=10, score=-1.000
force tagging i=13, score=0.655
force tagging i=14, score=-1.000
force tagging i=15, score=0.502
force tagging i=16, score=-1.000

('이건 진짜 좋은 영화 라라랜드진짜 좋은 영화',
 [0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, None, None, 1, 0, 1, 0, 1])

‘진짜’의 앞, 뒤를 반드시 띄어도 된다는 사용자 지식을 모델에 입력하고 싶습니다. RuleDict 는 이를 위한 ‘사용자 띄어쓰기 사전’입니다. 사용자 사전은 텍스트 파일로 저장되며 tap separated two columns 입니다.

예시의 space_rules.txt는 다섯가지 단어에 대하여 띄어쓰기 규칙을 hard coding 할 수 있도록 넣어둔 사전입니다.

가령	101
진짜	101
가게는	1001
가게로	1001
가게야	1001

‘진짜’라는 단어의 태그는 101 입니다. 앞/뒤는 반드시 띄어쓰라는 의미입니다. RuleDict 에 이용할 파일이 하나라면 str 의 path 를, 두 개 이상이라면 list of str 을 입력합니다.

from soyspacing.countbase import RuleDict

# rule_fnames = './space_rules.txt'
rule_fnames = ['./space_rules.txt']

rule_dict = RuleDict(rule_fnames)

아래처럼 substring 의 앞,뒤의 태그를 미리 저장해둔 뒤, string match 를 이용하여 강제적으로 tagging 을 수행합니다.

print(rule_dict.rule_dict)

# {'가게는': (1, 0, 0, 1),
#  '가게로': (1, 0, 0, 1),
#  '가게야': (1, 0, 0, 1),
#  '가령': (1, 0, 1),
#  '진짜': (1, 0, 1)}

RuleDict 를 이용하여 해당 문장을 교정하면 ‘진짜’의 앞, 뒤도 모두 띄어짐을 볼 수 있습니다.

model.correct(sent, verbose, mc, ft, nt, st, rules=rule_dict)

# ('이건 진짜 좋은 영화 라라랜드 진짜 좋은 영화',
#  [0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1])

Limitation of Conditional Random Field

이 챕터는 Conditional Random Field (CRF) 에 대한 이해를 전제로 씌였습니다. CRF 에 대한 내용은 이전의 CRF 포스트를, CRF 를 이용한 띄어쓰기 교정에 관련된 내용은 이전의 crf 를 이용한 한국어 교정 포스트를 참고하세요.

CRF 는 주어진 에 가장 적합한 을 찾기 위하여 다음과 같이 을 정의합니다.

시점의 feature 를 만드는 potential function 입니다. 이 부분이 1 이라면 만큼의 점수가 더해집니다. ‘C[-3:0] = 지금나가’ 라는 글자 다음에 띈 경우가 많았다면 ‘C[-3:1] = 지금나가요’ 인 상황에서도 만큼의 점수가 더해집니다. 2016-10-20 뉴스를 기준으로 이처럼 한쪽 문맥만 보았을 때와 양쪽 모든 문맥을 보았을 때의 결과가 다른 경우는 4.757 % 였습니다. 물론 다양한 features 를 이용하기 때문에 4.757 % 만큼 오류가 발생하는 것은 아닙니다만, 어느 상황에서나 같은 features 를 이용하는 것은 좋지 않습니다.

이 부분 때문에 CRF 가 공격적인 띄어쓰기 패턴을 보입니다.

Performance

soyspacing 과 CRF-based space correction 의 성능을 비교합니다. 이를 위해서는 noise 가 정확히 태깅되어 있는 데이터가 필요합니다. 뉴스로부터 띄어쓰기가 되어 있는 부분에 대하여 특정 확률로 띄어쓰기를 제거하였습니다. 아래 표의 첫번째 컬럼은 학습데이터의 띄어쓰기 오류 정도이며, 두번째부터 마지막 컬럼까지는 테스트 데이터의 띄어쓰기 오류 비율입니다.

아래 표의 두번째 rows 의 마지막 column 의 값 8.475 는 띄어쓰기 오류가 1 % 있는 학습데이터를 이용하여 학습한 모델을 노이즈가 50 % 있는 데이터에 적용하였을 때의 남은 띄어쓰기 오류 비율입니다.

remain noise (soyspacing)
trn / test 1 2 3 5 10 15 20 30 40 50
1 0.203 0.341 0.52 0.854 1.731 2.581 3.424 5.125 6.807 8.475
2 0.175 0.41 0.527 0.866 1.753 2.615 3.47 5.197 6.901 8.591
3 0.177 0.351 0.638 0.88 1.785 2.665 3.534 5.294 7.028 8.75
5 0.183 0.363 0.554 1.097 1.844 2.752 3.649 5.467 7.259 9.027
10 0.203 0.4 0.61 1.009 2.508 3.044 4.033 6.039 8.02 9.978
15 0.231 0.454 0.692 1.144 2.308 4.323 4.572 6.843 9.089 11.314
20 0.269 0.534 0.81 1.341 2.701 4.041 6.805 8.024 10.656 13.27
30 0.433 0.861 1.312 2.166 4.351 6.513 8.67 16.217 17.277 21.554
40 0.798 1.581 2.391 3.969 7.945 11.927 15.908 23.861 34.821 39.839
50 0.896 1.777 2.691 4.465 8.921 13.413 17.88 26.829 35.773 46.123

아래는 CRF 를 이용한 띄어쓰기 교정기의 남은 오류 비율입니다.

remain noise (crf based)
trn / test 1 2 3 5 10 15 20 30 40 50
1 0.164 0.334 0.508 0.833 1.681 2.493 3.29 4.871 6.39 7.85
2 0.122 0.245 0.361 0.592 1.178 1.747 2.28 3.302 4.241 5.093
3 0.143 0.28 0.435 0.696 1.401 2.072 2.716 3.987 5.184 6.321
5 0.192 0.373 0.567 0.936 1.87 2.784 3.669 5.416 7.097 8.706
10 0.272 0.532 0.812 1.332 2.731 3.972 5.245 7.765 10.195 12.527
15 0.363 0.715 1.092 1.792 3.584 5.488 7.11 10.591 13.996 17.328
20 0.355 0.701 1.07 1.759 3.511 5.261 7.198 10.348 13.648 16.868
30 0.42 0.827 1.26 2.074 4.137 6.2 8.209 12.754 16.133 19.97
40 0.55 1.092 1.653 2.728 5.428 8.138 10.786 16.078 22.158 26.369
50 0.685 1.362 2.055 3.406 6.789 10.178 13.535 20.214 26.796 34.296

여기서 우리는 두 가지를 확인할 수 있습니다. 첫째, soyspacing 이 CRF 기반 알고리즘보다 더 많은 띄어쓰기 오류를 남겨둡니다. Test data noise level - remain noise level 은 recall 입니다.

둘째는 학습데이터의 noise level 이 15 % 정도 된다 하여도 이를 학습하여 학습데이터에 그대로 적용하면 5.488 % 의 띄어쓰기 오류가 남습니다. 즉, 9.512 % 의 띄어쓰기 오류가 교정됩니다. 학습데이터에 20 ~ 30 % 의 노이즈가 있어도 띄어쓰기 교정은 어느 정도 이뤄짐을 알 수 있습니다. 빡빡하게 완벽에 가까운 학습데이터를 모을 필요가 없다는 의미입니다.

아래의 두 표는 본래 붙여써야 하는데 잘못 띄어쓴 경우입니다. 띄어쓰기 오류 교정기가 가장 신경써야 하는 부분입니다. soyspacing 이 띄어쓰기 오류를 좀 더 남겨두기는 하지만, 잘못 띄어쓰는 경우는 훨씬 적습니다.

false spacing (soyspacing)
trn / test 1 2 3 5 10 15 20 30 40 50
1 0.406 0.407 0.408 0.41 0.414 0.418 0.422 0.431 0.439 0.449
2 0.394 0.394 0.396 0.397 0.402 0.406 0.409 0.418 0.426 0.435
3 0.38 0.381 0.381 0.383 0.388 0.392 0.395 0.403 0.411 0.42
5 0.35 0.35 0.351 0.351 0.356 0.36 0.363 0.371 0.378 0.387
10 0.284 0.284 0.285 0.286 0.288 0.292 0.295 0.302 0.309 0.316
15 0.223 0.223 0.224 0.225 0.228 0.227 0.233 0.239 0.244 0.25
20 0.177 0.177 0.178 0.179 0.181 0.183 0.18 0.19 0.194 0.199
30 0.084 0.084 0.084 0.084 0.085 0.086 0.087 0.081 0.089 0.091
40 0.028 0.028 0.028 0.028 0.029 0.029 0.029 0.028 0.024 0.03
50 0.015 0.015 0.015 0.015 0.015 0.015 0.015 0.015 0.015 0.012
false spacing (crf based)
trn / test 1 2 3 5 10 15 20 30 40 50
1 1.149 1.154 1.159 1.168 1.192 1.213 1.238 1.285 1.331 1.377
2 1.726 1.735 1.745 1.764 1.815 1.863 1.916 2.017 2.117 2.224
3 1.278 1.284 1.289 1.299 1.327 1.353 1.382 1.437 1.491 1.546
5 0.941 0.946 0.949 0.957 0.978 0.996 1.017 1.058 1.097 1.139
10 0.549 0.552 0.555 0.561 0.578 0.591 0.609 0.641 0.673 0.704
15 0.345 0.347 0.349 0.353 0.363 0.373 0.383 0.405 0.425 0.444
20 0.376 0.378 0.38 0.385 0.395 0.405 0.416 0.437 0.458 0.476
30 0.23 0.231 0.232 0.235 0.242 0.248 0.255 0.269 0.281 0.293
40 0.126 0.126 0.127 0.128 0.132 0.134 0.137 0.144 0.15 0.155
50 0.059 0.059 0.059 0.06 0.062 0.063 0.065 0.069 0.073 0.076

이를 바탕으로 우리는 F1 score 를 계산할 수 있습니다. 각각의 train, test data 의 noise level 마다 F1 score 를 계산한 뒤, 이에 평균을 취하면 F1-macro 를 계산할 수 있습니다.

1 % 부터 50 % 까지의 모든 종류의 noise level 에 대한 학습데이터를 이용하였을 경우, soyspacing 과 CRF based 의 F1 macro 는 0.698 과 0.693 입니다. 비등비등합니다.

하지만 학습데이터의 noise level 이 50 % 이면 애초에 학습데이터로 이용해선 안됩니다. 이 포스트의 첫 부분에서 띄어쓰기용 학습데이터를 고를 때에는 첫번째로 vocabulary distribution 을 고려하고, 두번째로 띄어쓰기가 어느 정도 되어 있는 문장을 선별해야 함을 언급하였습니다. 여유있게 학습데이터의 noise level 이 30 % 까지 될 수 있다고 가정하여, 1 ~ 30 % 의 noise level 을 지닌 학습 데이터로 모든 noise level 의 테스트 데이터를 교정하였을 때의 F1 macro 는 soyspacing 이 0.808, CRF-based 가 0.731 입니다.

_ soyspacing CRF based
F1 macro (all) 0.698 0.693
F1 macro % train 0.808 0.731

soyspacing 은 CRF 보다 덜 공격적으로 띄어쓰기 교정을 합니다.