[개발이야기] 나랏말싸미 NLP를 만나다 - 텍스트 전처리 편

"블루펭귄 개발 과정"

Posted by 예띠 on December 16, 2019

블루펭귄 개발 과정

주식회사 와들의 첫 iOS 앱인 블루펭귄을 개발하는 과정을 담은 글이다.

나랏말싸미 다른 나랗와 달아

한글은 음소 문자로서, 자음과 모음이 나뉘어 있다. 그러나, 로마자와 같은 일반적인 음소 문자와 달리 자음을 초성, 중성, 종성으로 모아서 음절 단위로 쓴다. 흐르는 물을 보고 한글은 ‘ㅁㅜㄹ’이 아닌 ‘물’이라 쓰고 로마자는 ‘water’이라고 쓰는 것이 일례가 된다. 그렇기 때문에, 한글은 음절 문자와 비슷한 성격을 가지기도 한다.

한글은 교착어라는 특징도 있다. 한글은 한 형태소에 다른 형태소를 결합하여 다양한 문장을 만든다. 예를 들어 ‘나는 저녁을 먹었다’에서 ‘저녁을’은 ‘저녁’이라는 명사와 ‘을’이라는 조사가 결합하여 목적어로 실현된다. 만약 조사가 ‘만’, ‘은’으로 바뀐다면 새로운 문장은 이전의 문장과 다른 뜻을 갖는다.

그렇다면, 텍스트 전처리란

텍스트 전처리는 텍스트라는 데이터를 각종 프로그램에 넣기 전에 컴퓨터가 이해하기 쉬운 방식으로 변환을 해주는 것이다. 한글 텍스트를 전처리하기에 앞서 전처리를 하지 않으면 어떤 문제가 생기는지 생각해보자.

  1. 텍스트에 ‘노이즈’가 상태로 프로그램에 전달된다. 보통 NLP에 쓰는 데이터셋은 인터넷에서 크롤링한 기사, 온라인 백과사전, 블로그 포스트 등을 활용한다. 여기에는 HTML 태그, 문자 이모티콘, 맞춤법 오류 등 글의 구조와 내용을 파악하는데 방해되는 요소들이 많다. (물론, 이모티콘을 잘 활용한다면 문장의 긍정, 부정을 파악하는 데 도움이 될 수는 있다.) 그래서, 텍스트를 임베딩하거나, 언어 분석 모델에 임베딩된 문자열들을 넣기 전에 전처리를 통해 노이즈를 제거해야한다.

  2. 같은 의미의 단어가 전혀 다른 단어로 분류될 수 있다. 한글은 교착어이다. 그래서, 프로그램에서 단순하게 띄어쓰기로 문장을 나눈다면 ‘밥이 맛있었다’와 ‘맛있는 밥을 먹었다’는 문장은 ‘밥이/맛있었다’와 ‘맛있는/밥을/먹었다’로 구분할 수 있다. 그런데, 단어들에 대해 더 이상 처리를 해주지 않으면 컴퓨터는 ‘밥이’와 ‘밥을’, ‘맛있었다’와 ‘맛있는’ 등을 각각 다른 어휘로 분류할 것이다. 하지만, 이들은 명사와 조사, 어간과 어미의 결합으로 만들어진 어휘이다. 두 문장들을 형태소로 나누면 ‘밥’, ‘맛있-‘이라는 같은 뜻을 가진 형태소를 발견할 수 있는 것이다. 따라서, 한글 텍스트는 형태소 분석까지 해주어야 임베딩, 모델 학습을 효과적으로 진행할 수 있다.

텍스트 전처리하기

이제 한글 텍스트의 전처리를 진행해보자.

1. HTML 태그 제거하기

만약 현재 텍스트를 HTML 소스에서 크롤링한 것이라면 ‘', '’ 모양의 텍스트를 제거해야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# data.txt 파일의 HTML 태그를 제거한다.

def erase_tag(line):
    i = line.find("<")
    if i != -1:
        j = line[i+1:].find(">")
        if j != -1:
            line = line[:j]
            erase_tag(line)

    else:
        return line    

with open("data.txt", "r") as f:
    raw_lines = f.readlines()

with open("data_no_tags.txt", "w") as f:
    for line in raw_lines:
        line = erase(line)
        f.write(line + "\n")
2. 이모티콘, 불필요한 문자열 제거하기

’^_^’, ‘;;’, ‘@_@’ 등의 이모티콘들은 한글이나 숫자가 포함되지 않은 문자이다. 이모티콘 이외에도 문장에 불필요한 문자들이 있을 수 있으므로 정규식 표현을 사용하여 이들을 없앤다.

1
2
3
4
import re

# s라는 str의 문자를 편집한다.  
s = re.sub('\s+', ' ', s.replace("/[^가-힣ㄱ-ㅎㅏ-ㅣa-zA-Z0-9]/gi", "")))
3. 맞춤법 오류

인터넷 기사, 온라인 백과사전에서 크롤링한 글들은 맞춤법이 틀린 문장의 수가 적지만 블로그 포스트나 댓글은 잘못 띄어쓰거나 문장 부호가 없어 문장의 구분을 알 수 없는 경우가 많다. (철자가 틀린 경우는 임베딩에서 보정한다.) 이러한 경우 kss라는 모듈의 split_sentences()를 이용한다.

1
2
3
4
5
import re
import kss

# 문장이 끝날 때 '.', '!', '?' 이외에 쓸 수 있는 문자들을 정규표현식에 넣은 것으로 다른 문자들을 추가할 수도 있다.
s = kss.split_sentences(" ".join(re.split(';|ㅋ|ㅎ|ㅠ|\n|•', s)))
4. 형태소 분석

일반적으로 형태소 분석은 konlpy.tag의 다양한 클래스 중 하나를 선택한다.
참고: 형태소 분석기들의 성능 비교하기

1
2
3
4
5
6
7
8
9
10
11
from konlpy.tag import Komoran

tagger = Komoran()
sentence = "나는 와들의 개발자입니다."

poses = tagger.pos(sentence) 
# result: [('나', 'NP'), ('는', 'JX'), ('와', 'NNG'), ('들', 'XSN'), ('의', 'JKG'), ('개발자', 'NNP'), ('이', 'VCP'), ('ㅂ니다', 'EF'), ('.', 'SF')]


morph = tagger.morphs(sentences)
# result: ['나', '는', '와', '들', '의', '개발자', '이', 'ㅂ니다', '.']

“와들”을 하나의 고유 명사로 인식하게 하려면 user_dic.txt 파일에
“와들 NNP”을 추가하여 tagger = Komoran(userdic="user_dict.txt")라고 정의하면된다.

조금 더 나아간다면

임베딩 편에서 설명할 예정이지만 fastText라는 단어 임베딩 모델을 쓴다면, 한글을 형태소를 너머 자모까지 분석하기도 한다. “나는 와들의 개발자입니다.”라는 문장의 ‘개발자’를 “ㄱㅐ-ㅂㅏㄹ-ㅈㅏ”로 분리하여 단어 임베딩 모델에 집어넣는 것이다. 그렇게 되면 ‘개발자’의 맞춤법을 ‘게발자’라고 잘못 쓴 경우에도 임베딩 모델은 ‘개발자’와 ‘게발자’를 비슷한 어휘로 취급하게 된다.

앞으로

다음 편에서는 전처리한 한글 텍스트 데이터를 임베딩 모델에 학습시켜볼 예정이다.

참고자료

[1] 한국어 임베딩