대화체에 유연한 띄어쓰기 모델 만들기

How to make Spacing Model for chatting language

서수인 김준성 | 2019년 08월 05일

자연어 형태의 텍스트를 컴퓨터가 이해하기 하기 위해서는 토크나이징 (tokenizing) 과정이 필수적으로 진행되어야 합니다. 그러나 핑퐁팀에서 다루고 있는 채팅 데이터는 띄어쓰기가 제대로 안 된 데이터가 많고 이로 인해 토크나이징 단계에서 오류가 발생하기도 합니다. 어떻게 하면 채팅 문장의 띄어쓰기를 수정할 수 있을까요?

Table of Contents

  1. 문제 소개
  2. 띄어쓰기의 특성
    1. 영어의 경우
    2. 한국어의 경우
    3. 한국어 채팅 데이터의 경우
    4. 요약
  3. 모델 조사 및 코딩
    1. 기존 모델 조사
    2. 모델 설계 과정
  4. 실험 세팅
    1. 데이터 준비
    2. 훈련 데이터 생성 전략
    3. 띄어쓰기 평가 방법
  5. 실험 결과 및 분석
    1. 기존 모델 성능 비교
    2. 토크나이징 결과 비교
  6. 띄어쓰기 라이브러리 ChatSpace
  7. 결론
    1. 정리
    2. 이후 과제
    3. 이 글을 마치면서

1. 문제 소개

우리가 사용하는 텍스트를 컴퓨터가 이해하기 용이하게 작은 단위로 쪼개는 작업을 토크나이징이라고 합니다. 토크나이징에 대한 자세한 내용은 이전 포스팅의 Introduction 에서 이주홍 님께서 설명해 놓은 글이 있으니 이를 참조해주세요. 문장이 제대로 토크나이징 되려면 그 문장의 띄어쓰기가 잘 이루어져야 합니다. 그러나 채팅 문장에서는 띄어쓰기를 생략하는 경우가 많으며, 이 때 토크나이징 결과가 올바르게 출력되지 않기도 합니다. 아래는 띄어쓰기가 안 된 실제 채팅 데이터와 그 문장을 Mecab 으로 토크나이징한 결과입니다.

입력 문장 Mecab 결과 원하는 결과
저녁머글준비즁 녁머글준비즁 저녁 머글 준비 중
왱 항댸마신다니까 왱 항 댸마신다니까 왱 항 댸 마신 다니까
공부개열심히하네 공부 개열 심히 하 네 공부 개 열심히 하 네
헤헤 비안올거예요 헤헤 비안 올 거 예요 헤헤 비 안 올 거 예요
굿모닝 난가는중 굿모닝 난가 는 중 굿모닝 난 가 는 중

위에 예들은 실제 채팅에서 사용된 문장의 일부이며 토크나이징이 제대로 되지 않는 것을 확인할 수 있습니다. 채팅 데이터는 (1) 문어체가 아닌 구어체로 이루어져 문법적으로 맞지 않는 비문이 많고 (2) 오타가 많으며 (3) 띄어쓰기 자체가 잘 이루어지지 않는다는 문제가 있습니다. 이런 문장을 올바르게 토크나이징하려면 어떻게 해야 할까요?

가장 좋은 방법은 문장을 맞춤법에 맞춰 수정하는 것입니다. 문장의 맞춤법을 교정하면 아래와 같이 토크나이징 결과가 올바르게 수정되는 것을 볼 수 있습니다. 그러나 채팅에서만 사용되는 어휘나 표현의 특성을 잃을 수 있고, 맞춤법 전체를 교정하는 모델을 만드는 것은 너무 복잡한 일입니다.

원래 문장 맞춤법을 수정한 문장 Mecab 결과
저녁머글준비즁 저녁먹을준비중 저녁 먹 을 준비 중
왱 항댸마신다니까 왜 한대마신다니까 왜 한 대 마신 다니까
공부개열심히하네 공부정말열심히하네 공부 정말 열심히 하 네
헤헤 비안올거예요 헤헤 비(가/는)안올거에요 헤헤 비 (가/는) 안 올 거 예요
굿모닝 난가는중 굿모닝 난 가는 중 굿모닝 난 가 는 중

우리는 문장의 띄어쓰기를 교정한다면 토크나이징 성능을 향상 시킬 수 있다는 가정을 세웠습니다. 문장이 올바르게 토크나이징 되지 않는 이유는 여러가지가 있지만, 그 중 띄어쓰기는 단어를 구분하는 기준으로 사용되기 때문에 토크나이징에 영향을 줄 수 있다고 생각했습니다. 이를 위해 채팅 데이터에 적합한 띄어쓰기 모델을 구현하고자 했습니다. 이 포스팅에서는 다음 내용을 다루고 있습니다.

2. 띄어쓰기의 특성

지피지기(知彼知己)면 백전백승(百戰百勝)이라는 말이 있듯이 띄어쓰기를 수정하는 모델을 만들기 위해서는 먼저 띄어쓰기의 특성에 대해서 알고 있어야 합니다. 먼저 한국어와 영어를 비교하여 두 언어에서 띄어쓰기 특성의 차이점에 알아보도록 하겠습니다.

영어의 경우

englishisawestgermaniclanguage… (생략)

영어는 띄어쓰기가 의미 인식에 큰 영향을 주는 언어 중 하나입니다. 위 예시의 경우 한 문장의 띄어쓰기를 모두 제거하니 의미를 파악하기 어렵고 시간도 오래 걸립니다. 그 이유는 영어가 단어 하나하나가 변하지 않고 뜻과 형태를 완전하게 가지고 있는 고립어이기 때문입니다. (위키페디아의 ‘English Language’ 페이지의 첫번째 문장입니다)

다른 재미있는 예시로 트위터 해시태그 사건이 있습니다. 2013년 4월 8일 마가랫 대처 영국 수상이 사망하면서 #nowthatchersdead라는 해시태그가 트위터에서 퍼졌습니다. 그런데 이 해시태그는 다음 2가지 경우로 해석 될 수 있었습니다.

nowthatchersdead

→ now Thatcher’s dead -(1)

→ now that Cher’s dead -(2)

(1)의 경우 “이제 Thatcher는 죽었어” 라고 해석할 수 있고, (2)의 경우 “이제 Cher가 죽었어” 라고 해석할 수 있습니다. 원래 해시태그의 의도는 (1)이지만 해시태그는 현대 영어에서 유일하게 띄어쓰기를 사용하지 않는 표현법을 채택하고 있습니다. 이 문제로 인해 미국 가수 Cher의 일부 팬들은 이를 (2)로 해석하여 한때 혼란을 겪기도 했습니다. 1 이 사례를 통해서도 영어에서 띄어쓰기가 얼마나 중요한지 알 수 있습니다.

한국어의 경우

한국어는 어근에 접사가 붙는 교착어입니다. 즉, 뜻을 가지고 있는 부분에 문법적 기능을 가진 부분이 붙어서 단어가 만들어집니다. 접사(특히 조사)는 단어와 단어를 분리하는 기준이 되기 때문에, 영어와 비교하였을 때 띄어쓰기가 문장의 의미에 영향을 끼치는 경우는 적습니다. 그러나 한글 또한 분명히 띄어쓰기에 따라 의미가 변하는 경우가 존재하기 때문에 정확한 의미 전달을 위해서는 띄어쓰기의 원칙을 따르는 것이 중요합니다. 아래는 띄어쓰기의 유무에 관하여 널리 사용되어 온 예시입니다.

아버지가방에들어가신다.

→ 아버지 가방에 들어가신다. -(1)

→ 아버지가 방에 들어가신다. -(2)

우리는 상식적으로 사람이 가방에 들어갈 수 없다는 사실을 알기 때문에 (1)은 틀리고 (2)가 맞다고 판단할 수 있습니다. 그러나 실제로 두 문장 모두 문법적으로는 이상이 없습니다. 띄어쓰기에 따라서 의미가 달라지는 예시를 더 들어보겠습니다.

나물좀주세요.

→ 나 물 좀 주세요. -(1)

→ 나물 좀 주세요. -(2)

무지개같은사장님

→ 무지개 같은 사장님 -(1)

→ 무지 개 같은 사장님 -(2)

위의 “아버지가방에들어가신다” 예시와는 다르게 아래의 예시들은 의미적/문법적으로 이상이 없기 때문에 어느 쪽으로도 해석될 수 있습니다. 아마 평범한 한국어 화자라면 둘 다 가능하다고 말할 것입니다.

한국어 채팅 데이터의 경우

이처럼 띄어쓰기는 가독성 뿐만 아니라 의미 전달에 중요한 역할을 하지만, 스캐터랩에서 수집한 채팅 데이터에서는 띄어쓰기가 생략된 문장을 빈번히 발견할 수 있었습니다. 사람들이 띄어쓰기를 안 하는 이유는 무엇일까요? 우리는 그 이유를 크게 두 가지로 보았습니다.

  1. 가독성 보단 속도

    • 채팅이라는 특성상 상대방에게 자신의 의사를 빨리 전달하는 것을 우선시하게 됩니다. 그렇기 때문에 시간이 오래 드는 띄어쓰기를 생략하여 더 빨리 상대방에게 채팅을 전달하기 때문이라고 보았습니다. 귀찮다는 이유도 있죠.


  2. 띄어쓰기가 의미의 영향을 주지 않아서

    • 두번째로는 띄어쓰기의 영향이 적은 구나 문장의 경우 띄어쓰기를 생략하는 경우입니다. 띄어쓰기 실수 중 가장 많은 경우가 “~을/를 안 하다 (좋다/사다/가다 등)” 를 “~을/를 안하다 (안좋다/안사다/안가다 등)” 로 쓰는 경우 입니다. 작성자가 올바른 띄어쓰기 방법을 알고 있다 하더라도 띄어쓰기의 여부가 문장의 의미나 의도 전달에 영향을 주지 않기 때문에 생략하는 경우가 대부분이며, 실제로 이로 인한 의사소통 문제가 발생하지 않기 때문에 문제시 되지 않고 있습니다.

요약

위에서는 한국어에서의 띄어쓰기의 특성 및 채팅 데이터에서 띄어쓰기가 제대로 지켜지지 않는 이유에 대해서 알아보았습니다. 사람들이 의사소통을 할 때에는 띄어쓰기를 생략해도 의미 전달에 크게 문제가 없으나, 이러한 문장을 데이터로써 처리할 때 문제가 발생할 수 있습니다. 이런 문제를 해결하고자 채팅 데이터의 띄어쓰기 특성을 반영하는 모델을 학습하여 문장의 띄어쓰기를 수정하여 처리하고자 합니다.

3. 모델 조사 및 코딩

기존 모델 조사

한국어의 띄어쓰기를 교정하는 오픈소스 모델에는 KoSpacingRAWS가 있으며, 둘 다 CNN과 RNN으로 이루어진 모델입니다. 두 모델의 차이점으로 KoSpacing은 CNN과 RNN을 직렬로 연결하였고, RAWS는 이들을 병렬로 연결하여 학습하였다는 점이 있습니다. 각 모델에 대해 자세히 살펴 보겠습니다.

KoSpacing은 n-gram의 정보와 단어의 시퀀스 정보를 활용하여 문장의 띄어쓰기를 교정하도록 신경망을 구성한 모델입니다. n-gram의 특성을 반영하기 위해 각각 다른 윈도우 크기를 가지는 CNN들을 모델링 하였고, 이를 전부 합친 후 (concatenation) 여러 개의 CNN이 압축한 정보를 RNN을 통해 순차적으로 모델링하였습니다. Fasttext 방법으로 기학습된 단어 임베딩을 사용하였기 때문에, 임베딩 사전에 포함되어 있지 않은 단어의 경우 “<unk>” 토큰으로 대체되는 문제점이 있습니다.

KoSpacing 모델 구조. [[출처]](https://github.com/haven-jeon/PyKoSpacing)

RAWS는 문자 단위로 토크나이징을 하여 모든 문자에 대한 임베딩을 생성하였으며, 문자 임베딩의 시퀀스를 각 CNN과 RNN으로 모델링한 후 합치는 구조를 가지고 있습니다. 두 가지의 다른 특성을 앙상블하는 아이디어를 사용하여 표현력은 KoSpacing 보다 우수하지만, CNN과 RNN 사이에 병목 현상이 발생하여 실시간 입출력을 구현하기 어려운 구조입니다. 또한 학습 시 띄어쓰기가 제거된 문장만을 입력하도록 문제를 설정하였기 때문에, 테스트 시 기존 문장의 띄어쓰기를 활용하지 못하는 문제점이 있습니다.

RAWS 모델 구조. [[출처]](https://github.com/warnikchow/raws)

모델 설계 과정

우리는 기존 모델들의 장점을 취하여 더 좋은 모델 구조를 구성하고자 했습니다. KoSpacing 에서는 CNN-RNN 구조를, RAWS 에서는 문자 수준 임베딩 기법을 차용하여 기본적인 모델을 구성하였습니다. KoSpacing 모델은 길이가 긴 텍스트를 고려하여 여러 개의 CNN을 병렬로 구성하였지만, 채팅 데이터는 길이가 매우 짧기 때문에 (20~30 글자 이하) 여러 개의 CNN을 직렬로 연결하였습니다. 추가로 계층적 (hierarchcal) 정보를 반영하기 위해 각 CNN 레이어의 출력을 이어 붙인 (concatenate) 벡터를 다음 CNN의 출력으로 넘겨주었습니다. CNN 이후에는 각 데이터의 순서 정보를 반영하기 위해 RNN을 사용하였고, 학습 속도를 향상시키기 위하여 CNN 뒤에 BatchNorm 레이어를, RNN 뒤에는 LayerNorm 레이어를 추가하였습니다.

chatspace 모델 구조. CNN 을 계층적으로 구성하여 심층적인 특성을 추출하고 이를 RNN을 통해 시간순으로 반영하도록 구성한 모델

4. 실험 세팅

데이터 준비

띄어쓰기 모델을 학습하고, 성능을 테스트하기 위해서는 (띄어쓰기가 제대로 안 된 문장과 띄어쓰기가 교정된 문장) 쌍이 필요합니다. 먼저 스캐터랩에서 보유하고 있는 채팅 데이터 중 특정 길이 이하의 3127개 문장을 무작위로 추출하였습니다. 그 다음 이 문장들의 띄어쓰기를 사람이 직접 교정하여 3127개의 문장 쌍의 테스트 데이터를 생성하였습니다. 추가로 테스트 데이터에 포함된 띄어쓰기가 성능에 어느 정도의 영향을 미치는지를 알아보기 위해 세 개의 그룹으로 테스트 데이터를 분할하였습니다. 각 데이터 분류의 기준 및 예시는 아래와 같습니다.

예시)

​ original: 아직도 집에가면 아빠가 머리말려주는데

​ target: 아직도 집에 가면 아빠가 머리 말려주는데 (공백 개수: 5개)

​ easy: 아직도 집에 가면아빠가 머리말려주는데 (공백 개수: 3개)

​ normal: 아직도집에 가면 아빠가머리말려주는데 (공백 개수: 2개)

​ hard: 아직도집에가면아빠가머리말려주는데 (공백 개수: 0개)

훈련 데이터는 다음 2가지 데이터를 함께 사용하였습니다.

데이터의 특성 상, 띄어쓰기를 하지 않아야 하는 곳에 띄어 쓴 경우 보다, 띄어써야 할 부분을 띄지 않은 경우가 훨씬 많았습니다. 이러한 특성을 반영하기 위해서는 RAWS 모델 처럼 띄어쓰기가 없는 문장에서 띄어쓰기를 복원하는 것으로 문제를 설정할 수도 있지만, 이미 존재하는 띄어쓰기에 대한 정보를 잃어버리지 않는 것이 더 바람직합니다. 따라서 기존 문장의 띄어쓰기를 남겨두되, 이와 같이 한 문장 내에 이미 띄어 쓰여진 공백은 참 (ground truth)이라고 가정하고 모델을 학습하였습니다.

훈련 데이터 생성 전략

특정 문장의 특정 띄어쓰기 패턴에 bias가 걸리지 않도록 하기 위해 훈련 epoch 이 진행될 때마다 특정 확률값에 따라 띄어쓰기의 잔여 여부를 무작위로 결정하도록 훈련 데이터를 생성했습니다. 가능한 출력 개수는 입니다. (n = 공백 개수) 특정 한곗값(threshold)을 각 문장 입력마다 다르게 하여 문장 출력의 다양성을 높였습니다.

sentence = "이 문장은 예시 문장이다"
for each epoch:
    for char in sentence:
        if random() < threshold and char == ' ':
            del char
sentence_1 = "이 문장은예시 문장이다"
sentence_2 = "이문장은 예시문장이다"
sentence_3 = "이문장은예시문장이다"
# ...
# sentence_n

띄어쓰기 평가 방법

문장의 띄어쓰기 표현 방법에는 여러 방법이 있지만 가장 일반적인 방법인 띄어쓰기의 이진 표현(binary representation)을 사용하였습니다. 다음 글자가 공백인 경우 1로 아닌 경우에 0으로 표현하는 것이며 아래는 이를 python 코드로 표현한 것입니다.

def binary_representation(sentence):
	return [1 if sentence[i+1] == ' ' else 0 for i in range(len(sentence)-1)]

sentence = "임의의 문장" #len(sentence) = 6
binary_representation(sentence) = [0, 0. 1. 0. 0] #len = 5

sentence = "임 의 의 문 장" #len(sentence) = 9
binary_representation(sentence) = [1, 0. 1. 0. 1, 0, 1, 0] #len = 8

# len(sentence) == len(binary_representation(sentence)) + 1 is True

띄어쓰기 정보를 포함한 리스트는 (문장의 길이 - 1) 만큼의 크기를 가지며 0과 1로만 구성되어 있습니다. 실제 문장에 띄어쓰기의 이진표현을 적용한 경우는 다음과 같습니다.

우리는랜선연애중이야

→ [0, 0, 0, 0, 0, 0, 0, 0, 0]

우리는 랜선 연애 중이야

→ [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]

그러나 이 경우 공백의 개수에 따라 이진 표현의 길이가 달라져 버리기 때문에 띄어쓰기가 다른 두 문장을 비교하는 데 적합하지 않습니다. 그렇기 때문에 훈련 과정이 아닌 성능 테스트 시에는 다음 표현의 이진 표현을 사용합니다.

def binary_representation_for_test(sentence):
	output = list()
	for i in range(len(sentence)-1):
		if sentence[i] = ' ':
			continue
		if sentence[i+1] == ' ':
			output.append(1)
		else:
			output.append(0)
	return output

sentence = "임의의 문장" #len([char for char in sentence if char != ' ']) = 5
binary_representation_for_test(sentence) = [0, 0. 1. 0] #len = 4

sentence = "임 의 의 문 장" #len([char for char in sentence if char != ' ']) = 5
binary_representation_for_test(sentence) = [1, 1. 1, 1] #len = 4

# len(sentence_w/o_whitespace) ==
#	len(binary_representation_for_test(sentence_w/o_whitespace)) + 1 is True

위의 테스트용 이진 표현을 사용하면 공백의 개수가 달라 비교하고 싶은 문장의 전체 길이가 다르더라도 각 문장에 대한 띄어쓰기 표현 방식이 길이는 동일하기 때문에 두 문장 간의 띄어쓰기를 비교할 수 있습니다. 그러나 입력 문장에 남아있는 띄어쓰기에 대한 정보가 사라지기 때문에 학습 과정에서는 이 표현을 사용하지 않습니다.

우리는랜선연애중이야

→ [0, 0, 0, 0, 0, 0, 0, 0, 0]

우리는 랜선 연애 중이야

→ [0, 0, 1, 0, 1, 0, 1, 0, 0]

위는 테스트용 이진 표현을 적용한 문장의 예시 입니다. 첫 번째 문장과 두 번째 문장은 띄어쓰기만 다른 두 문장으로, 테스트용 이진 표현을 적용하면 두 문장의 띄어쓰기를 반영한 리스트의 길이가 같기 때문에 두 문장 간 오차를 계산할 수 있게 됩니다.

5. 실험 결과 및 분석

제안 모델 세부사항

위의 제안한 모델을 학습시킬 때 사용한 하이퍼파라미터는 다음과 같습니다.

하이퍼파라미터
vocab size 3563
character-embedding dimension 256
CNN window size 3, 7, 5
RNN type Bidirectional LSTM
RNN hidden size 64
learning rate 0.001
optimizer Adam

띄어쓰기를 하지 않는 경우와 띄어쓰는 경우의 빈도수에서 불균형 (non_whitespace character : whitespace_character)이 존재하기 때문에 loss 를 계산할 때 각 클래스의 가중치를 두어 학습한 경우에 대하여도 테스트를 진행하였습니다.

기존 모델 성능 비교

먼저 기존의 띄어쓰기 교정 모델을 이용하여 테스트 데이터에 대한 성능을 평가하였습니다. 평가 척도로는 이진 분류(binary classification)에 대한 정확도(accuracy), 정밀도(precision), 재현율(recall), f1 점수를 사용하였습니다.

비교 모델
  1. ADAMS.ai 의 띄어쓰기 API (Saltlux)
  2. RAWS (Real-time Automatic Word Segmentation)
  3. KoSpacing (w/ pre-trained embedding and weights)
  4. 부산대 맞춤법 검사기 (띄어쓰기만 반영)
  5. Daum 맞춤법 검사기 (띄어쓰기만 반영)
  6. 네이버 맞춤법 검사기 (띄어쓰기만 반영)
  7. KoSpacing (w/ character-level embedding, 실험 모델과 같은 데이터로 훈련)

다른 띄어쓰기 api 와 비교하여 띄어쓰기 모델이 어느 정도의 성능을 도출하는지 비교하였습니다. 1~6 까지의 모델들은 공개된 API를 그대로 사용하였기 때문에 훈련에 사용된 데이터셋이 다를 수 있습니다.

평가 지표 설명

정밀도와 재현율에 사용되는 true postive, true negative, false positive, false negative 에 대하여 간단히 설명하고자 합니다. 출력 결과가 0 또는 1인 이진 분류 문제이기 때문에, 정답과 예측값 사이의 빈도수를 다음 행렬과 같이 표현할 수 있습니다.

  정답이 0인 경우 정답이 1인 경우
0으로 예측한 경우 true negative false negative
1로 예측한 경우 false positive true positive

위 표에서 true positive 와 true negative 는 각 클래스 0과 1에 대해 모델이 알맞게 예측한 경우입니다. 반대로 정답이 0이지만 1로 예측한 경우를 false positive, 정답이 1이지만 0으로 예측한 경우를 false negative 라고 합니다.

위 표를 참조하여 평가지표를 표현하면 다음과 같습니다.

Easy 결과
Metrics acc pre rec F1 diff.count diff.ratio (%)
ADAMS2 0.8811 0.8988 0.7629 0.8253 2229 71.2824
RAWS2 0.933 0.9089 0.9091 0.909 1514 48.417
KoSpacing_pre 0.9569 0.9178 0.9697 0.943 1159 37.0643
PSU 0.9655 0.9564 0.9496 0.953 918 28.3572
Daum 0.9703 0.9601 0.9592 0.9597 753 24.08
Naver 0.9748 0.9562 0.9762 0.9661 718 22.9613
KoSpacing_custom 0.9819 0.9702 0.9755 0.9728 - -
pingpong-chatspace3 0.9818 0.9844 0.966 0.9751 560 17.9085
pingpong-chatspace 0.9802 0.9657 0.9810 0.9733 606 19.3796
Normal 결과
Metrics acc pre rec F1 diff.count diff.ratio (%)
ADAMS2 0.8811 0.8988 0.7629 0.8253 2229 71.2824
RAWS2 0.933 0.9089 0.9091 0.909 1514 48.417
KoSpacing_pre 0.94 0.9184 0.9188 0.9186 1458 46.62
PSU 0.926 0.9484 0.845 0.8937 1745 55.8043
Daum 0.9426 0.9377 0.9042 0.9207 1174 37.544
Naver 0.9663 0.9501 0.9587 0.9544 1296 41.4455
KoSpacing_custom 0.9687 0.9636 0.9257 0.9443 - -
pingpong-chatspace3 0.9646 0.9788 0.9238 0.9505 997 31.8836
pingpong-chatspace 0.9703 0.9624 0.9567 0.9596 842 26.9268
Hard 결과
Metrics acc pre rec F1 diff.count diff.ratio (%)
ADAMS2 0.8811 0.8988 0.7629 0.8253 2229 71.2824
RAWS2 0.933 0.9089 0.9091 0.909 1514 48.417
KoSpacing_pre 0.9153 0.9191 0.8441 0.88 1808 57.819
PSU 0.7909 0.9206 0.4729 0.6248 2196 70.2271
Daum 0.9018 0.9107 0.8129 0.859 1619 51.7749
Naver 0.9513 0.941 0.9257 0.9333 1046 33.4506
KoSpacing_custom 0.9475 0.9483 0.9257 0.9369 - -
pingpong-chatspace3 0.9461 0.9723 0.8786 0.9231 1372 43.8759
pingpong-chatspace 0.9547 0.9570 0.9182 0.9372 1140 36.4567


각 테스트 데이터에 대한 결과 비교 (기존 모델로는 Naver 만을 비교)

위 전체 표 및 차트를 통해 알 수 있듯이, pingpong-chatspace 모델이 다른 모델들에 비해 가장 높은 F1 점수를 기록하는 것을 알 수 있습니다. 이는 우리의 모델이 대화체 텍스트의 띄어쓰기 오류를 가장 잘 교정할 수 있다는 것을 의미합니다. 클래스 가중치를 두어 학습한 경우, 기존 pingpong-chatspace 보다 높은 정밀도를, easy 데이터에 대해서는 가장 높은 F1 점수를 얻었습니다. 그러나 normal, hard 데이터에서는 재현율이 상대적으로 큰 폭으로 감소하면서 기존 pingpong-chatspace 모델보다 낮은 F1 점수를 기록하였습니다.

토크나이징 결과 비교

띄어쓰기 모델을 이용하여 띄어쓰기 오류를 수정한 문장을 토크나이징 했을 때 결과가 어떻게 변하는지를 확인해봅시다. 문제 소개에서 든 예시에 우리가 제안하는 모델을 적용하여 결과를 확인해보았습니다.

원래 문장 띄어쓰기를 수정한 문장 Mecab 결과 이전 결과
저녁머글준비즁 저녁 머글준비즁 저녁 머글 준비 즁 녁머글준비즁
왱 항댸마신다니까 왱 항댸 마신다니까 왱 항 댸 마신 다니까 왱 항 댸마신다니까
공부개열심히하네 공부개 열심히 하네 공부 개 열심히 하 네 공부 개열 심히 하 네
헤헤 비안올거예요 헤헤 비 안올거에요 헤헤 비 안 올 거 에요 헤헤 비안 올 거 예요
굿모닝 난가는중 굿모닝 난 가는 중 굿모닝 난 가 는 중 굿모닝 난가 는 중

띄어쓰기 모델을 통해 수정한 문장을 토크나이징 한 결과, 원래 문장을 토크나이징 했을 때 발생한 오류가 해결된 경우를 찾을 수 있었습니다. 띄어쓰기를 교정함으로써 오타를 포함하지 않은 단어들의 의미를 어느 정도 분리할 수 있었습니다. (저녁, 열심히, 비 등)

원래 문장 띄어쓰기를 수정한 문장 Mecab 결과 이전 결과
배치고사 잘쳐 배치고 사 잘쳐 배치 고 사 잘 쳐 배치 고사 잘 쳐
나초먹고 싶어 나 초 먹고 싶어 나 초 먹 고 싶 어 나초 먹 고 싶 어
블루레이고장났어 블루레이고 장났어 블루 레 이 고 장 났 어 블루 레이 고장났 어

이와 반대로 띄어쓰기를 수정해서 토크나이징 결과에 악영향을 주는 경우 또한 존재합니다. 위 표에 제시된 문장들은 굳이 띄어쓰기를 교정하지 않아도 제대로 토크나이징 되는 문장들이지만, 띄어쓰기 모델의 오류로 인해 오히려 토크나이징이 잘못될 수 있습니다. 이러한 경우는 하나의 명사 내에 어미 (조사 포함)와 대명사 등, 구어체에 빈번하게 출현하는 문자를 포함할 때 자주 발생합니다.

오류가 발생한 단어 모델이 예상한 패턴 잘못 띄어쓴 결과
배치고사 ~치고, ~ 배치고 사
나초 나(,) ~ 나 초
블루레이고장 이고(,) ~ 블루레이고 장

띄어쓰기 모델의 영향을 정량적으로 알아보기 위해 기존 테스트 문장과 수정된 문장을 각각 토크나이징하여 출력이 다른 경우를 살펴보았습니다. 이를 통하여 띄어쓰기를 수정하는 경우 토크나이징 결과가 더 정확한지, 안 좋은지, 또는 영향이 없는 지 등을 비교하였습니다.

평가 지표 설명
토크나이징 평가 결과
Metrics easy normal hard
diff_count 146 257 418
diff_ratio 4.67% 8.22% 14.26%
better_count 81 162 302
better_ratio 55.48% 63.04% 72.25%
worse_count 39 55 69
worse_ratio 26.71% 21.40% 16.51%
both_wrong 9 16 19
no_matter 17 24 28

전체 문장 중에서 토크나이징 결과가 다른 경우는 약 5~15 % 정도를 차지하였으며 이 중 절반 이상이 띄어쓰기 모델을 적용한 경우가 더 좋은 결과를 나타내는 것을 확인할 수 있었습니다. 가설대로 띄어쓰기를 수정하는 것으로 토크나이징 성능을 향상시킬 수 있었고, 이는 특히 띄어쓰기가 되지 않은 hard 데이터 에서 두드러지게 나타났습니다. 테스트 문장 수가 많지 않아 차이가 미미해 보이나 수많은 문장의 처리에 적용한다면 어느 정도 유용한 결과를 낼 수 있을 것으로 예측됩니다.

6. 결론

정리

지금까지 띄어쓰기 오류가 많은 채팅 데이터를 제대로 토크나이징하기 위한 띄어쓰기 모델의 구현 과정에 대해 설명하였습니다. 이 모델을 사용하여 채팅 데이터나 구어체 텍스트의 띄어쓰기를 교정하거나 토크나이징 성능을 향상시키는데 사용할 수 있습니다. 또한 실험 및 분석을 통하여 약 2.5 ~ 7.5 % 이상의 문장의 토크나이징 성능이 향상되는 것을 검증하였습니다.

이후 과제

앞에서 띄어쓰기 모델이 토크나이징 결과에 악영향을 주는 단어는 주로 고유명사나 외래어 등의 저빈도 단어를 포함하는 것을 알 수 있어습니다. 이러한 경우에 띄어쓰기 모델이 잘못 띄어쓰게 된다면 앞서 언급한 것처럼 이후 토크나이징에 큰 영향을 미칠 수 있습니다. 아래 예시는 테스트 데이터를 수정한 문장 및 그 문장에 대한 토크나이징한 결과입니다.

원래 문장: 그래비티말고그냥가벼운거볼까?

띄어쓰기된 문장: 그래 비티 말고 그냥 가벼운거 볼까?

원래 문장 토크나이징 결과: 그래비티 말 고 그냥 가벼운 거 볼까 ?

띄어쓴 문장 토크나이징 결과: 그래 비 티 말 고 그냥 가벼운 거 볼까 ?

원래 문장: 내장지방수치도너무높고

띄어쓰기된 문장: 내 장지방 수치도 너무 높고

원래 문장 토크나이징 결과: 내장 지방 수치 도 너무 높 고

띄어쓴 문장 토크나이징 결과: 내 장 지방 수치 도 너무 높 고

위의 첫번째 예시에서 띄어쓰기 모델은 그래비티 (gravity)를 “그래(,)비티”로 인식하여 띄어 쓴 문장을 출력하고 있습니다. 그 결과 이 문장을 토크나이징 했을 때 “그래비티”라는 단어가 반영되지 않습니다. 두번째 예시에서는 “내장지방(内臓脂肪)” 을 “내(나의) 장지방”으로 인식하고 있습니다. 이 문장의 토크나이징 결과인 “내(나의) 장 지방 수치” 라는 구가 문법적으로 오류는 없지만, 용례상으로 “내장지방”을 가리키는 것이기 때문에 저빈도 단어에 대한 조치가 앞으로 이 모델을 향상시키는데 필요한 과정이라고 생각됩니다.

띄어쓰기 라이브러리 ChatSpace

저희는 이런 과정을 거쳐서 만든 띄어쓰기 모델을 오픈소스로 공개하기로 하였습니다. 좋은 성능을 보여주는 띄어쓰기 모델(네이버, 카카오)은 API 자체가 공개 되어 있지 않고 무료로 사용할 수 없게 되어 있습니다. 하지만 띄어쓰기 교정은 한국어를 다루는 NLP 앱에서는 굉장히 중요한 기능이고 특히 댓글, 대화, 리뷰 등 실제로 활용할 수 있는 분야가 굉장히 넓기 때문에 유용하게 사용될 수 있을 것 같습니다.

from chatspace import ChatSpace

spacer = ChatSpace()
spacer.space("안녕 만나서반가워 내이름은뽀로로라고해")
# '안녕 만나서 반가워 내 이름은 뽀로로라고 해'

모델은 pytorch 로 개발 되어 있으며, 모델은 torch.jit을 이용하여 packaging 되어 있습니다. 물론 torch.jit이 공식적으로 지원되지 않는 torch<1.1.0에서도 사용이 가능합니다. 또한 batch-size, torch.device 등을 custom 하게 조정하실 수 있어서 대용량의 데이터에 모델을 적용하실 경우에도 유용하게 사용하실 수 있습니다.

ChatSpace 깃허브 : https://github.com/pingpong-ai/chatspace

다양한 한국어 NLP 문제를 풀고 있는 개발자 커뮤니티에 이 ChatSpace 프로젝트가 도움이 되었으면 하는 바입니다. 버그나 질문, 추가 기능 요청 사항은 깃허브 이슈에 올려주세요 :)

이 글을 마치면서

채팅 데이터를 처리하기 위한 띄어쓰기 모델에 대해 정리해 보았습니다. 전처리 과정은 귀찮지만 가장 중요한 단계라고 해도 과언이 아닙니다. 이 과정이 제대로 이루어지지 않는다면 어떤 모델을 만들어도 잘못된 형식의 데이터를 입력하게 됩니다. 따라서 전처리 과정은 컴퓨터에게 언어를 이해시키는 가장 첫 발걸음이자 도약이며, 이 과정을 구현하면서 뿌듯함을 느낄 수 있었습니다.

아쉽게도 학습용 데이터는 공개할 수 없지만, 테스트용 데이터는 핑퐁 AI 깃허브에 저장되어 있습니다. 더 좋은 띄어쓰기 모델을 개발하실 때 유용하게 사용하시면 좋을 것 같습니다. 긴 글 읽어주셔서 감사합니다 :)

  1. https://www.cbsnews.com/news/twitter-hashstag-nowthatcherisdead-confuses-cher-fans/ 

  2. easy, normal, hard 와 관련 없이 같은 성능  2 3 4 5 6

  3. 클래스 가중치를 {0: 2/3, 1:1/3} 으로 학습시킨 모델  2 3