트위터 파헤치기 시리즈 두번째 - 한국어 전처리

트위터 파헤치기 두번째 시리즈의 주제는 한국어 전처리 이다.
단순히 LDA 토픽 모델링을 하는 것에서부터 딥러닝 모델을 훈련시키는 것 까지 NLP와 관련된 전 과정에서 전처리는 필수적이다.
텍스트 데이터의 특성상 기본적으로 노이즈 (noise)가 많은 데이터에 속하기 때문이다. 다음과 같은 과정을 보통 진행한다.

  • Tokenization : 문장을 하나의 기준을 가지고 자르는 것 (안녕하세요 -> 안녕 / 하세요)
  • Stemming : 동사 및 형용사 등의 활용 형태를 원문으로 바꾸는 것 (잼따 -> 재미 이다)
  • Noramlization : 표기가 다른 단어들을 통합하는 것 (입니닼ㅋㅋ -> 입니다 ㅋㅋ)
  • Noise removal : 문장의 의미를 이해하는 데에 불필요한 부분을 제거하는 것 (특수문자, url 등)

영어의 경우 nltk 라는 강력한 파이썬 패키지가 존재하여, 대부분의 우리가 원하는 전처리 함수들이 구현되어 있다.
그러나 한국어의 경우 Tokenization의 단위가 단어인지, 형태소인지, 자모인지, 아니면 그 외인지조차 아직까지 불명확한 부분이 있다. (현재도 연구중)
또한, Normalization이나 Stemming의 경우에도 Konlpy에 최근 포함된 Okt 클래스가 지원하지만 써본 결과 영어만큼 완벽하지는 않다.

그래서 본 포스트에서는 제일 general 한 토크나이저인 Mecab을 사용해서 그나마 reasonable한 방향으로 한국어 전처리를 진행해 보고자 한다. 현재도 계속 연구중이므로 완벽한 방법은 아니라는 것에 유의하자.

토크나이저 설치하기

전처리를 시작하기 전, base 도구가 될 토크나이저 선정 및 설치는 필수적이다.
다양한 토크나이저가 있지만, 속도가 빠르고 비문에서도 적당히 잘 동작하는 Mecab 패키지를 본 포스트에서는 사용할 것이다.
Khaiii 또는 Soynlp와 같은 최신 한국어 대응 패키지들을 사용하여도 무방하다.
심지어 한국어가 아닌 다른 나라 언어 (예: 이란어)를 전처리 하고 싶을 때에도 아래의 로직은 그대로 활용할 수 있다. 적절한 토크나이저만 선택해서 설치하자.

우리가 사용할 Mecab공식 홈페이지에서 설치 방법을 확인할 수 있다. 상세한 설치 방법은 여기서는 다루지 않겠다. 총 3가지의 설치 과정을 모두 진행해야 한다는 것만 유의하자.

  • Mecab-ko 설치하기 (source)
  • Mecab-ko-dic 설치하기 (사전)
  • Mecab-python3 설치하기 (파이썬 연동)

포스트를 적는 도중 나 대신 Mecab 설치 방법에 대해 자세히 적어놓은 블로그를 발견했다. 이수진 님의 블로그 - Mecab 설치 을 참고해도 괜찮을 것 같다.

설치 완료 후 주피터 노트북에서 동작을 확인하자. 혹시나 Konlpy를 설치하지 않은 사용자라면 이 패키지는 한국어 처리의 기본이니 설치해 놓자.

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

m = Mecab()
m.pos("일등이 아니여도 괜찮아")

# print out
>> [('일등', 'NNG'),
('이', 'JKC'),
('아니', 'VCN'),
('여도', 'EC'),
('괜찮', 'VA'),
('아', 'EC')]

텍스트 클리닝

본격적으로 한국어 토크나이징을 진행하기 전, noise removal 파트부터 코드를 작성해 보자.

이전 포스트 - 트윗 수집하기에서 수집했던 트윗을 예시로 텍스트 클리닝을 진행할 것이다.

위 그림은 트위터에서 직접 눈으로 볼 수 있는 트윗의 페이지를 캡쳐한 것이다. 그러나 우리가 수집한 데이터는 아래와 같이 생겼다.

상당히 텍스트 데이터의 noise가 심한 것을 확인할 수 있다. 특수문자 및 URL이 텍스트 중간 중간에 섞여 있어 제대로 제거하지 않으면 이후 분석에 방해가 된다.

이제 텍스트 클리닝을 수행하는 코드를 실제로 작성해 보자. 주로 regex 문법을 활용할 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import re

# Basic Cleaning Text Function
def CleanText(readData, Num=False, Eng=False):

# Remove Retweets
text = re.sub('RT @[\w_]+: ', '', readData)

# Remove Mentions
text = re.sub('@[\w_]+', '', text)

# Remove or Replace URL
text = re.sub(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+", ' ', text) # http로 시작되는 url
text = re.sub(r"[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{2,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)", ' ', text) # http로 시작되지 않는 url

# Remove Hashtag
text = re.sub('[#]+[0-9a-zA-Z_]+', ' ', text)

# Remove Garbage Words (ex. &lt, &gt, etc)
text = re.sub('[&]+[a-z]+', ' ', text)

# Remove Special Characters
text = re.sub('[^0-9a-zA-Zㄱ-ㅎ가-힣]', ' ', text)

# Remove newline
text = text.replace('\n',' ')

if Num is True:
# Remove Numbers
text = re.sub(r'\d+',' ',text)

if Eng is True:
# Remove English
text = re.sub('[a-zA-Z]' , ' ', text)

# Remove multi spacing & Reform sentence
text = ' '.join(text.split())

return text

코드가 좀 복잡하다. 그래도 위 코드는 내가 트윗을 연구 대상으로 삼으면서 여러모로 트위터에 맞게 세팅한 것이니 따라오길 바란다.

위 코드가 하는 역할은 최대한 원문을 살리면서 분석에 불필요한 (토픽을 이해하는 데에 필요하지 않은) 부분을 제거하는 것이다.
예를 들어서 트위터에서 리트윗 시 텍스트 앞에 RT @user_screenname 과 같은 형태의 텍스트가 붙게 되는데, 이런 부분만을 제거 한다던지 하는 것이다.

트위터에서 URL은 대단히 자주 보이는데, 우리가 분석할 때에는 필요치 않은 정보이다. 위 코드는 여러가지 경우의 URL을 찾아서 제거하게 해 놓았다. (Regex URL 매칭 후 제거)
또한 Boolean 을 사용해서 숫자와 영어에 대해서 옵션을 만들어 놓았는데, 이는 경우에 따라 숫자와 영어를 찾아서 모두 제거하고 한국어만 분석하는 것이 편하기 때문이다. 그러나 기본값은 False로 해 두었는데, 이를 활성화할 경우 KBS, CNN, 40개국, G20 과 같은 의미가 존재하는 단어들도 제거되기 때문이다.

트위터를 분석하지 않을 경우, 위 코드 중에서 일부만 사용하고 나머지는 주석 처리하면 편하게 적용할 수 있을 것이다. (요렇게 한국어 클리닝 코드를 통으로 올려놓은 블로그는 본적이 없어서.. 지금까지 나도 갑갑했다)

설명이 길었다. 위 코드를 앞의 예시 트윗에 적용해 보자.

1
2
3
4
5
6
7
SAMPLE_TEXT = "<🚨코로나19 가짜뉴스 팩트체크>\n\n신천지가 기성교회에 가서 코로나를 전파하라고 했다??!\n\n- 사실 무근입니다. \n신천지는 2월 18일 부터 전국 교회를 폐쇄하고 온라인\n예배로 전환하였습니다.\n\n#온라인예배\n#가짜뉴스_이제그만\n#신천지_팩트체크 pic.twitter.com/Dppie6iean"

print(f"Before cleaning text:\n{SAMPLE_TEXT}")
print("\n")
print(f"After cleaning text:\n{CleanText(SAMPLE_TEXT)}")
print("\n")
print(f"After cleaning text when Num is True:\n{CleanText(SAMPLE_TEXT, Num=True)}")

깔끔하게 텍스트 클리닝이 완료된 것을 확인할 수 있다.

텍스트 토크나이징

이번에는 위 텍스트 클리닝 코드를 포함한 한국어 토크나이징을 진행해 보겠다.
의미를 내포하고 있는 단위로 잘라서 토픽모델링을 시도하기 전 단계로 이해하면 된다.

Mecab 토크나이저는 POS 태깅을 지원하기 때문에, 태깅 상태를 보고 조사나 어미 등 의미를 파악하는 데에 불필요한 부분들을 제거한다.

추가적으로 위 프로세스에서 걸러지지 않은 Stopwords 를 추가로 거를 수도 있지만 이는 데이터에 따라서 커스터마이즈가 많이 이루어져야 하기에, 본 포스트에서는 생략했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from konlpy.tag import Mecab 

# Preprocessing code with Mecab
mecab = Mecab(dicpath="/usr/local/lib/mecab/dic/mecab-ko-dic") # Mecab User Dic Path

def preprocessing_mecab(readData):

#### Clean text
sentence = CleanText(readData)

#### Tokenize
morphs = mecab.pos(sentence)

JOSA = ["JKS", "JKC", "JKG", "JKO", "JKB", "JKV", "JKQ", "JX", "JC"] # 조사
SIGN = ["SF", "SE", "SSO", "SSC", "SC", "SY"] # 문장 부호
TERMINATION = ["EP", "EF", "EC", "ETN", "ETM"] # 어미
SUPPORT_VERB = ["VX"] # 보조 용언
NUMBER = ["SN"]

# Remove JOSA, EOMI, etc
morphs[:] = (morph for morph in morphs if morph[1] not in JOSA+SIGN+TERMINATION+SUPPORT_VERB)

# Remove length-1 words
morphs[:] = (morph for morph in morphs if not (len(morph[0]) == 1))

# Remove Numbers
morphs[:] = (morph for morph in morphs if morph[1] not in NUMBER)

# Result pop-up
result = []
for morph in morphs:
result.append(morph[0])

return result

위 코드에서 Mecab을 불러올 때 사용한 dicpath 옵션은 등록된 사용자 사전을 사용하고 싶을때 적는 것이다. 사용하지 않을 경우 지워버리면 된다.

코드의 흐름은 먼저 앞의 섹션에서 작성했던 CleanText() 함수를 사용해 텍스트 클리닝을 진행하고, 이후 Mecab 토크나이저를 사용해서 토큰화한다. 그리고 POS 태깅된 정보를 활용해서 의미가 없는 단어들을 삭제한다. 추가적으로, 잘랐을 때 길이가 1인 (글자 1개) 단어는 의미를 알기 어려워 제거하였다.

Mecab 토크나이저의 POS 태깅 정보를 보다 자세히 알고 싶다면 한글 형태소 품사 태그표를 참고하자.

이제 위 코드를 실행시켜 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SAMPLE_TEXT = "<🚨코로나19 가짜뉴스 팩트체크>\n\n신천지가 기성교회에 가서 코로나를 전파하라고 했다??!\n\n- 사실 무근입니다. \n신천지는 2월 18일 부터 전국 교회를 폐쇄하고 온라인\n예배로 전환하였습니다.\n\n#온라인예배\n#가짜뉴스_이제그만\n#신천지_팩트체크 pic.twitter.com/Dppie6iean"

# RUN!
preprocessing_mecab(SAMPLE_TEXT)

# Print out
>>> ['코로나',
'가짜',
'뉴스',
'팩트체크',
'신천지',
'기성',
'교회',
'코로나',
'전파',
'무근',
'입니다',
'신천지',
'전국',
'교회',
'폐쇄',
'온라인',
'예배',
'전환',
'온라인',
'예배',
'가짜',
'뉴스',
'이제',
'그만',
'신천지',
'팩트체크']

깔끔하게 토크나이징이 완료된 것을 확인할 수 있다. 읽어보면 의미도 잘 파악이 된다.
이제 위 코드들을 바탕으로, 다음 포스트에서는 LDA 토픽 모델링 및 간단한 워드 클라우드 만들기를 해볼 것이다.