개요
텍스트 데이터 정제는 데이터 과학과 머신러닝에서의 대표적인 전처리 작업이다. 이는 불용어 처리, 대문자와 특수문자 처리 등 덜 유용한 부분을 제거하는 과정을 포함한다. 불용어는 'the', 'a', 'an', 'in' 등 원하는 결과를 얻는데 불필요한 단어들을 말한다. 지금은 카프카의 저서인 '변신'의 텍스트를 정제해볼 것이다. 우선 파일을 열고 머리말과 꼬리말 정보를 삭제하고 'metamorphosis_clean.txt'라는 이름으로 파일을 저장하였다.
1) 데이터 미리 살펴보기
크기와 구조 같은 주요 특징을 살펴보면서 문장, 단락, 텍스트가 어떻게 이루어졌는지 확인한다.
'변신'의 경우에는
- 눈에 띄는 오타나 실수가 없다.
- 반점, 아포스트로피, 따옴표, 물음표 등의 문장 부호가 있다
- 전반적으로 단순하다.
2) 공백, 문장 부호, 대소문자 정규화하기
직접 텍스트 정제, NLTK 라이브러리 사용의 두 가지 방법이 있다. 보통 후자를 선호하지만, 텍스트 정제를 학습하기 위해 직접 정제하는 방법부터 살펴볼 것이다.
직접 텍스트 정제
몇 줄의 파이썬 코드로 직접 텍스트를 정제할 수 있다. '읽어들이기', '분할하기', '소문자로 바꾸기'의 세 가지 단계가 있다.
데이터 읽기
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
토큰화
토큰화는 단락을 문장으로 분할하거나 문장을 개별적인 단어로 분할하는 것을 말한다. 문장은 단어와 문장 부호로 분할될 수 있다. 가장 흔한 방법은 공백을 기준으로 단어를 나누는 것이다. 단어가 축약어, 생략어, 소유격일 경우 문제가 생길 수 있다(원래는 두 단어이지만, 축약하여 한 단어로 인식).
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
# 공백으로 나누기
words = text.split()
print(words[:30])
# Output
# ['One', 'morning,', 'when', 'Gregor', 'Samsa', 'woke', 'from', 'troubled', 'dreams,', 'he', 'found', 'himself', 'transformed', 'in', 'his', 'bed', 'into', 'a', 'horrible', 'vermin.', 'He', 'lay', 'on', 'his', 'armour-like', 'back,', 'and', 'if', 'he', 'lifted']
위 코드를 실행시키면 잘 동작하는 듯 보이지만, "wasn't", "dream," 등 문장 부호가 섞인 단어가 포함되어 있다. 따라서 텍스트 정제를 통해 문장 부호를 삭제한다.
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
# 단어로 분할하기
import re
words = re.split(r'\W+', text)
print(words[:30])
# Output
# ['One', 'morning', 'when', 'Gregor', 'Samsa', 'woke', 'from', 'troubled', 'dreams', 'he', 'found', 'himself', 'transformed', 'in', 'his', 'bed', 'into', 'a', 'horrible', 'vermin', 'He', 'lay', 'on', 'his', 'armour', 'like', 'back', 'and', 'if', 'he']
위 코드를 실행시키면 정규 표현식을 사용하여 문장 부호는 단어에 포함하지 않는다. '\W+'는 하나 이상의 단어 문자를 찾는다([a-zA-Z0-9_]+와 동일). 이 코드로 "wasn't"에서 "wasn"과 "t"를 얻을 수 있다.
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
# 공백으로 분할
words = text.split()
# 문장 부호 제거
import string
table = str.maketrans(", ", string.punctuation)
stripped = [w.translate(table) for w in words]
print(stripped[:30])
# Output
# ['One', 'morning', 'when', 'Gregor', 'Samsa', 'woke', 'from', 'troubled', 'dreams', 'he', 'found', 'himself', 'transformed', 'in', 'his', 'bed', 'into', 'a', 'horrible', 'vermin', 'He', 'lay', 'on', 'his', 'armourlike', 'back', 'and', 'if', 'he', 'lifted']
위 코드는 문자열 함수를 사용하여 텍스트 정제를 하였다. maketrans()는 str.translate()에 사용 가능한 변환 테이블을 반환한다. translate()는 주어진 변환 테이블을 통해 각 문자가 매핑된 문자열의 복사본을 반환한다. 최종적으로 "wasn't"에서 "wasnt"를 얻었다.
소문자화
텍스트는 보통 문장의 시작과 적절한 명사 강조를 반영하기 위해 대문자를 사용한다. 가장 흔한 접근법은 간단하게 모두 소문자로 바꾸는 것이지만, "US"를 "us"로 바꿀 때처럼 소문자로 바꾸는 것이 의미를 바꿀 수도 있다는 점을 명심해야 한다.
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
# 공백으로 분할
words = text.split()
# 문장 부호 제거
words = [word.lower() for word in words]
print(words[:30])
# Output
# ['one', 'morning,', 'when', 'gregor', 'samsa', 'woke', 'from', 'troubled', 'dreams,', 'he', 'found', 'himself', 'transformed', 'in', 'his', 'bed', 'into', 'a', 'horrible', 'vermin.', 'he', 'lay', 'on', 'his', 'armour-like', 'back,', 'and', 'if', 'he', 'lifted']
NLTK(Natural Language Toolkit) 라이브러리 사용
다음으로 NLTK를 활용한 텍스트 데이터 정제를 알아보자. 텍스트 작업과 모델링을 위한 파이썬 라이브러리로 고수준 api를 제공하여 다양한 정제 메소드를 유연하게 구현할 수 있도록 한다. 아래 링크를 참고하여 각 OS에 맞는 NLTK 설치가 가능하다.
import nltk
nltk.download('punkt')
코랩에서 바로 토큰화를 하려고 했는데, 에러가 발생해서 위의 코드를 우선 실행해주고 토큰화를 진행하였다.
토큰화
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
from nltk.tokenize import word_tokenize
tokens = word_tokenize(text)
print(tokens[:30])
# Output
# ['One', 'morning', ',', 'when', 'Gregor', 'Samsa', 'woke', 'from', 'troubled', 'dreams', ',', 'he', 'found', 'himself', 'transformed', 'in', 'his', 'bed', 'into', 'a', 'horrible', 'vermin', '.', 'He', 'lay', 'on', 'his', 'armour-like', 'back', ',']
결과에서 'looked', '.', "'s" 등을 확인할 수 있다. 그냥 봐서는 이전에 했던 다른 방법들과 차이가 별로 없어보이지만 '문장으로 분할하기'를 쉽게 처리할 수 있다. word_tokenize()를 sent_tokenize()로 대체하면 단어가 아닌 문장으로 분할이 가능하다.
문장 부호 필터링
isalpha()를 활용하여 문장 부호를 제거하고, 알파벳만 남길 수 있다.
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
from nltk.tokenize import word_tokenize
tokens = word_tokenize(text)
words = [word for word in tokens if word.isalpha()]
print(words[:30])
# Output
# ['One', 'morning', 'when', 'Gregor', 'Samsa', 'woke', 'from', 'troubled', 'dreams', 'he', 'found', 'himself', 'transformed', 'in', 'his', 'bed', 'into', 'a', 'horrible', 'vermin', 'He', 'lay', 'on', 'his', 'back', 'and', 'if', 'he', 'lifted', 'his']
3) 불용어 추출
주어진 텍스트에서 대부분의 단어는 주제, 목적, 의도를 보여주기 보다는 문장을 연결하기 위해 존재한다. 'the'나 'and'와 같은 단어는 불용어 리스트와의 비교를 통해 제거될 수 있다. word_tokenize()를 사용할 때처럼 stopwords() 사용하기 위해 다음 코드를 실행시켜준다.
import nltk
nltk.download('stopwords')
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
from nltk.tokenize import word_tokenize
tokens = word_tokenize(text)
tokens = [w.lower() for w in tokens]
# 문장 부호 제거
import string
table = str.maketrans('', '', string.punctuation)
stripped = [w.translate(table) for w in tokens]
from nltk.corpus import stopwords
stop_words = set(stopwords.words('english'))
words = [w for w in words if not w in stop_words]
print(words[:30])
# stop_words: ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', ...]
# Output
# ['One', 'morning', 'Gregor', 'Samsa', 'woke', 'troubled', 'dreams', 'found', 'transformed', 'bed', 'horrible', 'vermin', 'He', 'lay', 'back', 'lifted', 'head', 'little', 'could', 'see', 'brown', 'belly', 'slightly', 'domed', 'divided', 'arches', 'stiff', 'sections', 'The', 'bedding']
stop_words를 리스트 형태로 비교하더라도 결과는 똑같다. 그러나 집합 형태로 변경하면 연산속도가 O(N)에서 O(1)로 바뀌기 때문에 매우 빨라진다.
4) 어간 추출
어간 추출은 접미사같은 불필요한 문자를 빼는 방식으로 어미를 제거하여 단어의 어간만을 남기는 과정을 말한다. Porter나 Snowball과 같은 어간 추출 모델이 있다. 하지만 "universe"와 "university"가 "univers"라는 동일한 어간으로 줄어드는 것처럼 과도한 어간 추출의 위험성이 존재한다.
filename = 'metamorphosis_clean.txt'
file = open(filename, 'rt')
text = file.read()
file.close()
from nltk.tokenize import word_tokenize
tokens = word_tokenize(text)
from nltk.stem.porter import PorterStemmer
porter = PorterStemmer()
stemmed = [porter.stem(word) for word in tokens]
# Output
# ['one', 'morn', ',', 'when', 'gregor', 'samsa', 'woke', 'from', 'troubl', 'dream', ',', 'he', 'found', 'himself', 'transform', 'in', 'hi', 'bed', 'into', 'a', 'horribl', 'vermin', '.', 'he', 'lay', 'on', 'hi', 'armour-lik', 'back', ',']
PoterStemmer에 의한 어간 추출을 통해 "morning", "troubled"가 "morn", "troubl"로 변환되었다. 토큰이 소문자로 바뀐 것도 볼 수 있다.
5) 표제어 추출, 워드 임베딩/텍스트 벡터
표제어 추출은 어미와 어형변화를 제거하는 또 다른 방법이다. 텍스트의 부분을 결정하고 WordNet의 영어 어휘 데이터베이스를 활용하여 더 나은 결과를 얻을 수 있다. 그러나 속도는 더 느리다. 어간 추출은 데이터베이스의 쿼리에 더 유용한 반면에 표제어 추출은 텍스트 감성을 결정할 때 훨씬 더 나은 결과를 보여준다.워드 임베딩은 단어를 벡터로 나타내는 최신 방법이다. 워드 임베딩의 목적은 연속적인 높은 차원의 벡터를 찾는 것이다. 이 벡터는 높은 차원의 공간에서 의미상 서로 가까운 단어들의 관계를 나타낸다. Word2Vec와 GloVe는 텍스트를 벡터로 바꾸는 가장 일반적인 모델이다. 2차원 혹은 3차원 그래프로 나타내기 위해 주로 T-SNE(또는 PCA)를 사용하여 차원을 줄인다.
참고 링크
DataLit : 데이터 다루기
https://www.boostcourse.org/ds103/joinLectures/84465
카프카의 '변신' - 평문 UTF-8 다운로드
https://machinelearningmastery.com/clean-text-machine-learning-python/
NLTK 설치
https://www.nltk.org/install.html
Python 자료형(list, set, dictionary) 메서드 시간복잡도 정리
'프로젝트 단위 공부 > [부스트코스] DataLit : 데이터 다루기' 카테고리의 다른 글
Ch1-9. 데이터 시각화하기 (0) | 2023.07.21 |
---|---|
Ch1-7. 확률 변수 (0) | 2023.07.20 |
Ch1-6. 이산 확률 (0) | 2023.07.20 |
Ch1-2. SQL 테크닉 (0) | 2023.07.17 |
Ch1-1. 파이썬 정규표현식 다시보기 (0) | 2023.07.17 |