[Python] 머신러닝 완벽가이드 - 08. 텍스트 분석[소개]
Updated:
파이썬 머신러닝 완벽가이드 교재를 토대로 공부한 내용입니다.
실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.
기본 세팅
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
mpl.rc('font', family='NanumGothic') # 폰트 설정
mpl.rc('axes', unicode_minus=False) # 유니코드에서 음수 부호 설정
# 차트 스타일 설정
sns.set(font="NanumGothic", rc={"axes.unicode_minus":False}, style='darkgrid')
plt.rc("figure", figsize=(10,8))
warnings.filterwarnings("ignore")
1. 텍스트 분석 이해
1.1 텍스트 분석 주요 영역
1.1.1 텍스트 분류(Text Classfication)
문서가 특정 분류 또는 카테고리에 속하는 것을 예측하는 기법
예를 들어 특정 신문 기사 내용이 연애/정치/사회 중 어떤 카테고리에 속하는지 자동으로 분류한다.
스팸 메일 검출 같은 프로그램 등이 해당되며 지도학습을 적용한다.
1.1.2 감성 분석(Sentiment Analysis)
텍스트에서 나타나는 감정/판단/믿음/의견/기분 등의 주관적인 요소를 분석하는 기법
SNS에서 감정 분석, 영화나 제품에 대한 긍정 또는 리뷰, 여론조사 의견 분석등에 사용한다.
지도학습, 비지도학습 모두 가능하다.
1.1.3 텍스트 요약(Summarization)
텍스트 내에서 중요한 주제나 중심 사상을 추출하는 기법
대표적으로 토픽 모델링(Topic Modeling)이 있다.
1.1.4 텍스트 군집화와 유사도 측정
비슷한 유형의 문서에 대해 군집화를 수행하는 기법
텍스트 분류를 비지도학습으로 수행하는 방법의 일환으로 사용될 수 있다.
유사도 측정 역시 문서들간 유사도를 측정해 비슷한 문서끼리 모을 수 있는 방법이다.
1.2 텍스트 분석 수행 과정
1.2.1 텍스트 사전 준비작업(텍스트 전처리)
텍스트를 피처로 만들기 전에 미리 클렌징, 대/소문자 변경, 특수문자 삭제 등의 클렌징 작업,
단어(Word) 등의 토큰화 작업, 의미 없는 단어 제거 작업, 어근 추출 등의 텍스트 정규화 작업을 수행한다.
1.2.2 피처 벡터화/추출
사전 준비 작업으로 가공된 텍스트에서 피처를 추출하고 벡터 값을 할당한다.
대표적인 방법으로 BOW와 Word2Vec이 있으며 BOW는 Count 기반과 TF-IDF 기반 벡터화가 있다.
1.2.3 ML 학습/예측/평가
피처 벡터화된 데이터 셋에 ML 모델을 적용해 학습/예측/평가를 수행한다.
2. 텍스트 전처리
2.1 클렌징
불필요한 문자, 기호 등을 사전에 제거하는 작업이다.
예를 들어 HTML, XML 태그나 특정 기호 등을 사전에 제거한다.
2.2 텍스트 토큰화
2.2.1 문장 토큰화
from nltk import sent_tokenize
import nltk
# 마침표, 개행문자(\n) 등의 데이터 셋: 최초 한번만 다운
# nltk.download('punkt')
text_sample = 'The Matrix is everywhere its all around us, here even in this room. \
You can see it out your window or on your television. \
You feel it when you go to work, or go to church or pay your taxes.'
sentences = sent_tokenize(text = text_sample)
print(type(sentences),len(sentences))
print(sentences)
<class 'list'> 3
['The Matrix is everywhere its all around us, here even in this room.', 'You can see it out your window or on your television.', 'You feel it when you go to work, or go to church or pay your taxes.']
nltk
의sent_tokenize()
를 이용해 문장 단위로 토큰화가 가능하다.
2.2.2 단어 토큰화
from nltk import word_tokenize
sentence = "The Matrix is everywhere its all around us, here even in this room."
words = word_tokenize(sentence)
print(type(words), len(words))
print(words)
<class 'list'> 15
['The', 'Matrix', 'is', 'everywhere', 'its', 'all', 'around', 'us', ',', 'here', 'even', 'in', 'this', 'room', '.']
nltk
의word_tokenize()
를 이용해 단어 단위로 토큰화가 가능하다.
from nltk import word_tokenize, sent_tokenize
# 문장별로 단어 토큰화
def tokenize_text(text):
# 문장 토큰화
sentences = sent_tokenize(text)
# 문장별 단어 토큰화
word_tokens = [word_tokenize(sentence) for sentence in sentences]
return word_tokens
word_tokens = tokenize_text(text_sample)
print(type(word_tokens),len(word_tokens))
print(word_tokens)
<class 'list'> 3
[['The', 'Matrix', 'is', 'everywhere', 'its', 'all', 'around', 'us', ',', 'here', 'even', 'in', 'this', 'room', '.'], ['You', 'can', 'see', 'it', 'out', 'your', 'window', 'or', 'on', 'your', 'television', '.'], ['You', 'feel', 'it', 'when', 'you', 'go', 'to', 'work', ',', 'or', 'go', 'to', 'church', 'or', 'pay', 'your', 'taxes', '.']]
-
여러 문장을 문장별로 토큰화 하였다.
-
3개의 리스트를 가진 리스트가 반환되었고 각 개별 리스트는 문장별 단어 토큰화 요소를 가진다.
2.3 스톱 워드 제거
스톱 워드(Stop Word)는 분석에서 큰 의미가 없는 단어를 의미한다.
영어에선 is, the, a, will 등 필수 문법 요소이지만 문맥적으로 큰 의미가 없는 단어 등이 있다.
# 다양한 언어의 스톱 워드 목록 다운
# nltk.download('stopwords')
# 영어의 stopwords
stopwords_eng = nltk.corpus.stopwords.words('english')
print('영어 stop words 갯수:',len(stopwords_eng))
print(stopwords_eng[:20])
영어 stop words 갯수: 179
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're", "you've", "you'll", "you'd", 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his']
-
영어는 179개의 stopword가 존재한다.
-
이를 이용해서 앞서 문장별 단어 토큰화에 대해 stopword 제거를 수행해보자.
# 영어 stopword
stopwords = nltk.corpus.stopwords.words('english')
# 문장별 단어 토큰화 + stopword 제거
all_tokens = []
for sentence in word_tokens:
filtered_words=[]
# 문장 토큰의 각 단어 토큰
for word in sentence:
# 소문자 변환
word = word.lower()
# stopword 미포함
if word not in stopwords:
filtered_words.append(word)
all_tokens.append(filtered_words)
print(all_tokens)
[['matrix', 'everywhere', 'around', 'us', ',', 'even', 'room', '.'], ['see', 'window', 'television', '.'], ['feel', 'go', 'work', ',', 'go', 'church', 'pay', 'taxes', '.']]
- the, is 등의 stopword가 제거되었다.
2.4 어근추출
어근 추출(Stemming)과 원형 복원(Lemmatizaiton)은 단어의 원형을 찾는 기법이다.
Lemmatizaiton이 Stemming보다 정교하며 의미론적인 기반에서 원형을 찾는다.
2.4.1 Stemming
PorterStemmer
, LancasterStemmer
, SnowballStemmer
등 다양한 Stemmer가 있다.
from nltk.stem import LancasterStemmer
stemmer = LancasterStemmer()
print(stemmer.stem('working'),stemmer.stem('works'),stemmer.stem('worked'))
print(stemmer.stem('amusing'),stemmer.stem('amuses'),stemmer.stem('amused'))
print(stemmer.stem('happier'),stemmer.stem('happiest'))
print(stemmer.stem('fancier'),stemmer.stem('fanciest'))
work work work
amus amus amus
happy happiest
fant fanciest
-
work의 경우 진행형, 3인칭 단수, 과거형을 모두 기본 단어로 되돌렸다.
-
amuse는 기본 단어에 ing, s, ed등이 붙는게 아닌 amus에 붙으므로 amus로 되돌렸다.
-
happy, fancy의 경우도 제대로 원형을 찾지 못한다.
2.4.2 Lemmatization
from nltk.stem import WordNetLemmatizer
# nltk.download('wordnet')
lemma = WordNetLemmatizer()
# 동사: v, 형용사: a
print(lemma.lemmatize('amusing','v'),lemma.lemmatize('amuses','v'),lemma.lemmatize('amused','v'))
print(lemma.lemmatize('happier','a'),lemma.lemmatize('happiest','a'))
print(lemma.lemmatize('fancier','a'),lemma.lemmatize('fanciest','a'))
amuse amuse amuse
happy happy
fancy fancy
-
앞서 Stemming보다 정확하게 원형 단어를 추출하였다.
-
Lemmatization에선 정확한 원형 단어 추출을 위해 품사를 기입하여야 한다.
-
사전 기반으로 단어를 처리하므로 오류는 적으나 새로운 단어는 처리할 수 없다.
3. 피처 벡터화
3.1 BOW(Bag of Words)
BOW 모델은 모든 단어에 대해 빈도 값을 부여해서 피처 값을 추출하는 모델이다.
쉽고 빠른 구축이 가능하지만 단어의 순서를 고려하지 않으므로 문맥적인 의미 반영이 부족하다.
또한 희소 행렬(대부분의 값이 0)의 문제가 있다.
3.1.1 Count 벡터화
단순하게 모든 단어의 빈도로 피처 벡터화를 진행한다.
CountVectorizer()
를 이용해 행은 각 문장(문서)은 인덱스, 열은 단어로 피처 벡터화 가능하다.
CountVectorizer()
는 다음과 같이 작업을 수행한다.
-
소문자 일괄 변환
-
각 단어를 토큰화
-
텍스트 전처리(스톱 워드 제거)
-
피처 벡터화
Stemmer, Lemmatize는 tokenizer
파라미터 등으로 수행한다.
파라미터가 많아서 여기선 따로 적지 않는다. 교재를 참고하자.
from sklearn.feature_extraction.text import CountVectorizer
sen1 = ["I love you.", "because of you."]
vectorizer = CountVectorizer()
print(vectorizer.fit_transform(sen1).toarray())
print(vectorizer.vocabulary_)
[[0 1 0 1]
[1 0 1 1]]
{'love': 1, 'you': 3, 'because': 0, 'of': 2}
3.1.2 TF-IDF 벡터화
Count 벡터화는 일반적으로 자주 사용되는 단어까지 중요하게 인식하는 문제가 발생한다.
TF-IDF(Term Frequency Inverse Document Frequency)는 Count 벡터화의 문제를 보완한 방법이다.
각 문서별로 자주 나타나는 단어에 높은 가중치를 주되 모든 문서에서 자주 나타나는 단어에는 패널티를 부여한다.
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['because of you', 'I like you', 'what should I do ']
tfidfv = TfidfVectorizer().fit(corpus)
print(tfidfv.transform(corpus).toarray())
print(tfidfv.vocabulary_)
[[0.62276601 0. 0. 0.62276601 0. 0.
0.4736296 ]
[0. 0. 0.79596054 0. 0. 0.
0.60534851]
[0. 0.57735027 0. 0. 0.57735027 0.57735027
0. ]]
{'because': 0, 'of': 3, 'you': 6, 'like': 2, 'what': 5, 'should': 4, 'do': 1}
3.2 희소 행렬
모든 문서의 단어를 피처로 벡터화하면 컬럼이 많아질 수 밖에 없다.
하지만 대규모의 행렬이 생성되어도 단어의 종류에 비해 단어의 수는 제한적일 것이다.
그렇다면 행렬 원소의 대부분이 0으로 나타날 것이고 이러한 행렬을 희소 행렬이라 한다.
BOW 형태를 가진 언어 모델의 피처 벡터화는 대부분 희소 행렬이다.
희소 행렬은 불필요한 0값이 너무 많아 메모리 공간이 많이 필요하며 연산 시간도 오래 걸리게 된다.
이러한 희소 행렬이 적은 메모리 공간을 차지하게 변환하는 2가지 방법을 확인해보자.
3.2.1 COO 형식
COO(Coordinate: 좌표) 형식은 0이 아닌 데이터만 별도의 배열에 저장하고,
해당 데이터가 가리키는 행과 열의 위치를 별도의 배열에 저장한다.
from scipy import sparse
dense = np.array( [ [ 3, 0, 1 ],
[0, 2, 0 ] ] )
# 0이 아닌 데이터 배열
data = np.array([3,1,2])
# 데이터의 위치 배열
row_pos = np.array([0,0,1])
col_pos = np.array([0,2,1])
# COO 형식 희소 행렬 생성
sparse_coo = sparse.coo_matrix( ( data, (row_pos,col_pos) ) )
sparse_coo
<2x3 sparse matrix of type '<class 'numpy.int32'>'
with 3 stored elements in COOrdinate format>
sparse.coo_matrix()
를 이용하여 COO 형식의 희소 행렬 객체를 생성 가능하다.
sparse_coo.toarray()
array([[3, 0, 1],
[0, 2, 0]])
toarray()
를 이용해 다시 원래 형태의 행렬로 출력가능하다.
3.2.1 CSR 형식
COO 형식은 행과 열의 위치를 나타내기 위해 반복적으로 위치 데이터를 사용한다.
이를 해결한 방식이 CSR(Compressed Sparse Row) 형식이다.
간단하게 설명하면 COO에서 행 위치 배열을 다시 위치 시작 배열로 생성한다.
from scipy import sparse
dense2 = np.array([ [0,0,1,0,0,5],
[1,4,0,3,2,5],
[0,6,0,3,0,0],
[2,0,0,0,0,0],
[0,0,0,7,0,8],
[1,0,0,0,0,0] ])
# 0이 아닌 데이터 배열
data2 = np.array([1, 5, 1, 4, 3, 2, 5, 6, 3, 2, 7, 8, 1])
# 데이터의 위치 배열(행,열)
row_pos = np.array([0, 0, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 5])
col_pos = np.array([2, 5, 0, 1, 3, 4, 5, 1, 3, 0, 3, 5, 0])
# COO 형식 희소 행렬 생성
sparse_coo = sparse.coo_matrix( ( data2, (row_pos,col_pos) ) )
# 행 위치 배열의 고유한 값의 시작 위치 인덱스를 배열로 생성
row_pos_ind = np.array([0, 2, 7, 9, 10, 12, 13])
# CSR 형식 희소 행렬 생성
sparse_csr = sparse.csr_matrix( ( data2, col_pos, row_pos_ind ) )
print('COO 변환된 데이터가 제대로 되었는지 다시 Dense로 출력 확인')
print(sparse_coo.toarray())
print('CSR 변환된 데이터가 제대로 되었는지 다시 Dense로 출력 확인')
print(sparse_csr.toarray())
COO 변환된 데이터가 제대로 되었는지 다시 Dense로 출력 확인
[[0 0 1 0 0 5]
[1 4 0 3 2 5]
[0 6 0 3 0 0]
[2 0 0 0 0 0]
[0 0 0 7 0 8]
[1 0 0 0 0 0]]
CSR 변환된 데이터가 제대로 되었는지 다시 Dense로 출력 확인
[[0 0 1 0 0 5]
[1 4 0 3 2 5]
[0 6 0 3 0 0]
[2 0 0 0 0 0]
[0 0 0 7 0 8]
[1 0 0 0 0 0]]
-
COO에서 사용한 행 위치 배열에서 각 고유한 값의 시작 위치로 새로운 배열을 생성한다.
-
마지막에는 데이터의 총 항목 개수를 기입한다(여기선 13개).
-
두 방식 모두 원래 모습으로 행렬이 잘 출력된다.
dense3 = np.array([ [0,0,1,0,0,5],
[1,4,0,3,2,5],
[0,6,0,3,0,0],
[2,0,0,0,0,0],
[0,0,0,7,0,8],
[1,0,0,0,0,0] ])
coo = sparse.coo_matrix(dense3)
csr = sparse.csr_matrix(dense3)
- 실제 사용 시에는 위와 같이 원래 밀집 행렬을 생성 파라미터로 입력한다.
Leave a comment