ML

텍스트 분석

hyerimir 2022. 11. 22. 12:20

 

텍스트 분석은 머신러닝, 언어 이해, 통계 등을 활용해 모델을 수립하고 정보를 추출해 비지니스 인텔리전스(Business Intelligence)나 예측 분석 등의 분석 작업을 주로 수행

 

- 텍스트 분류(Text Classification)

: Text Categorization이라고도 하며 문서가 특정 분류 또는 카테고리에 속하는 것을 예측하는 기법을 통칭, 예를 들어 특정 신문 기사 내용이 연예/정치/사회/문화 중 어떤 카테고리에 속하는지 자동으로 분류하거나 스팸 메일 검출과 같은 프로그램 -> 지도학습 적용

- 감성 분석(Sentiment Analysis)

: 텍스트에서 나타나는 감정/판단/믿음/의견/기분 등의 주관적인 요소를 분석하는 기법을 총칭, 소셜 미디어 감정 분석, 영화나 제품에 대한 긍정 또는 리뷰. 여론조사 의견 분석 등의 다양한 영역에서 활용, Text Analytics에서 가장 활발하게 사용되고 있는 분야 -> 지도학습 + 비지도학습

- 텍스트 요약(Summarization)

: 텍스트 내에서 중요한 주제나 중심 사상을 추출하는 기법, 대표적으로 토픽 모델링(Topic Modeling)

- 텍스트 군집화(clustering)와 유사도 측정

: 비슷한 유형의 문서에 대해 군집화를 수행하는 기법, 텍스트 분류를 비지도 학습으로 수행하는 방법으로 일환으로 사용, 유사도 측정 역시 문서들간의 유사도를 측정해 비슷한 문서끼리 모을 수 있는 방법

 

텍스트 분석 수행 프로세스

1. 텍스트 사전 준비작업(텍스트 전처리)

: 텍스트를 피처로 만들기 전에 미리 클렌징, 대/소문자 변경, 특수문자 삭제 등의 클렌징 작업, 단어 등의 토근화 작업, 의미 없는 단어(stop word) 제거 작업, 어근 추출(Stemming/Lemmatization) 등의 텍스트 정규화 작업을 수행하는 것을 통칭

2. 피처 벡터화/추출

: 사전 준비 작업으로 가공된 텍스트에서 피처를 추출하고 여기에 벡터 값을 할당, 대표적인 방법으로 BOW, Word2Vec이 있으며, BOW는 대표적으로 Count 기반과 TF-IDF 기반 벡터화가 존재

3. ML 모델 수립 및 학습/예측/평가

: 피처 벡터화된 데이터 세트에 ML 모델을 적용해 학습/예측 및 평가를 수행

 

 

파이썬 기반의 NLP, 텍스트 분석 패키지

- NLTK(natural language toolkit for python)

:  파이썬의 가장 대표적인 NLP 패키지, 방대한 데이터 세트와 서브 모듈을 가지고 있으며 NLP의 거의 모든 영역을 커버하고 있음, 많은 NLP 패키지가 NLTK의 영향을 받아 작성되고 있음, 수행 속도 측면에서 아쉬운 부분이 있어서 실제 대량의 데이터 기반에서는 제대로 활용되지 못하고 있음

- Gensim

: 토픽 모델링 분야에서 가장 두각을 나타내는 패키지, 오래전부터 토픽 모델링을 쉽게 구현할 수 있는 기능을 제공해왔으며 Word2Vec 구현 등의 다양한 신기능도 제공, SpaCy와 함께 가장 많이 사용되는 NLP 패키지

- SpaCy

: 뛰어난 수행 성능으로 최근 가장 주목을 받는 NLP 패키지, 많은 NLP 애플리케이션에서 SpaCy를 사용하는 사례가 늘고 있음

 

 

텍스트 사전 준비 작업(텍스트 전처리) - 텍스트 정규화

텍스트 정규화 작업 : 클렌징, 토큰화, 필터링/스톱 워드 제거/철자 수정, stemming, lemmatization

 

1. 클렌징

: 텍스트에서 분석에 오히려 방해가 되는 불필요한 문자, 기호 등을 사전에 제거하는 작업

예를 들어 HTML, XML 태그나 특정 기호 등을 사전에 제거

 

2. 텍스트 토큰화

: 토근화의 유형은 문서에서 문장을 분리하는 문장 토큰화와 문장에서 단어를 토큰으로 분리하는 단어 토큰화로 나뉨

 

문장토큰화(sentence tokenization)

: 문장의 마침표(.), 개행문자(\n) 등 문장의 마지막을 뜻하는 기호에 따라 분리하는 것이 일반적, NLTK의 sent_tokenize를 이용

- 3개의 문장으로 이루어진 텍스트 문서를 문장으로 각각 분리하는 예제

from nltk import sent_tokenize
import nltk
#마침표, 개행 문자 등의 데이터 세트 다운로드
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 og to church or pay your taxes.'

sentences = sent_tokenize(text=text_sample)
print(type(sentences), len(sentences))
print(sentences)

 

단어 토큰화(Word Tokenization)

: 문장을 단어로 토큰화하는 것

일반적으로 문장 토큰화는 각 문장이 가지는 시맨틱적인 의미가 중요한 요소로 사용될 때 사용, 단어의 순서가 중요하지 않은 경우 문장 토큰화를 사용하지 않고 단어 토큰화만 사용해도 충분

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)

 

문서에 대해서 모든 단어를 토큰화

from nltk import word_tokenize, sent_tokenize

#여러 개의 문장으로 된 입력 데이터를 문장별로 단어 토큰화하게 만드는 함수 생성
def tokenize_text(text):
    
    #문장별로 분리 토큰
    sentences = sent_tokenize(text)
    #분리된 문장별 단어 토큰화
    word_tokens = [word_tokenize(sentence) for sentenc in sentences]
    return word_tokens
    
#여러 문장에 대해 문장별 단어 토큰화 수행
word_tokens = tokenize_text(text_sample)
print(type(word_tokens), len(word_tokens))
print(word_tokens)

word_tokens 변수는 3개의 리스트 객체를 내포하는 리스트

 

문장을 단어별로 하나씩 토큰화할 경우 문맥적인 의미는 무시될 수 밖에 없음, 이를 해결하고자 도입한 것이 n-gram

n-gram은 연속된 n개의 단어를 하나의 토큰화 단위로 분리해내는 것, n개 단어 크기 윈도우를 만들어 문장의 처음부터 오른쪽으로 움직이면서 토큰화를 수행

 

 

스톱 워드 제거

스톱 워드(stop word) : 분석에 큰 의미가 없는 단어

예를 들어 영어에서는 문장을 구성하는 필수 문법 요소지만 문맥적으로 큰 의미가 없는 단어를 의미

 

NLTK의 경우 가장 다양한 언어의 스톱 워드를 제공

 

import nltk
nltk.download('stopwords')

print(len(nltk.corpus.stopwords.words('english'))
print(nltk.corpus.stopwrods.words('english')[:20])

 

import nltk

stopwords = nltk.corpus.stopwords.words('english')
all_tokens = []

#위 예제에서 3개의 문장별로 얻은 word_tokens list에 대해 스톱 워드를 제거하는 반복문
for sentence in word_tokens:
    filtered_words = []
    #개별 문장별로 토큰화된 문장 list에 대해 스톱 워드를 제거하는 반복문
    for word in sentence:
        word = word.lower()
        #토큰화된 개별 단어가 스톱 워드이 단어에 포함되지 않으면 word_tokens에 추가
        if word not in stopwords:
            filtered_words.append(word)
    all_tokens.append(filtered_words)
    
print(all_tokens)

스톱 워드가 필터링을 통해 제거됐음을 알 수 있음

 

 

Stemming과 Lemmatization

: 문법적 또는 의미적으로 변화하는 단어의 원형을 찾는 것

 

Lemmatization이 Stemming보다 정교하며 의미론적인 기반에서 단어의 원형을 찾는다 -> 시간 더 많이 걸림

 

from nltk.stem import LancasterStemmer
stemmer = LancasterStemmer()

비교형, 최상급형으로 변형된 단어의 정확한 원형을 찾지 못하고 원형 단어에서 철자가 다른 어근 단어로 인식

 

from nltk.stem import WordNetLemmatizer
import nltk
nltk.download('wordnet')

lemma = WordNetLemmatizer()

일반적으로 Lemmatization은 보다 정확한 원형 단어 추추을 위해 단어의 '품사'를 입력해줘야 함

 

-> 앞의 Stemmer보다 정확하게 원형 단어를 추출

 

 

Bag of Words = BOW

Bag of Words 모델은 문서가 가지는 모든 단어(Words)를 문맥이나 순서를 무시하고 일괄적으로 단어에 대한 빈도 값을 부여해 피처 값을 추출하는 모델

 

장점

- 쉽고 빠른 구축

 

단점

- 문맥 의미(Semantic Context)반영 부족 : 단어의 순서를 고려하지 않기 때문에 문장 내에서 단어의 문맥적인 의미 무시됨, 이를 보완하기 위해 n_gram 기법을 활용할 수 있지만 제한적인 부분에 그침

- 희소 행렬 문제(희소성, 희소 행렬) : 많은 문서에서 단어를 추출하면 매우 많은 단어가 칼럼으로 만들어진다. 문서마다 서로 다른 단어로 구성되기에 단어가 문서마다 나타나지 않는 경우가 훨씬 더 많음

대규모의 칼럼으로 구성된 행렬에서 대부분의 값이 0으로 채워지는 행렬을 희소 행렬(Sparse Matrix)라고 함(반대로 0이 아닌 의미 있는 값으로 채워져 있는 행렬을 밀집 행렬이라고 함), 희소행렬은 일반적으로 ML알고리즘의 수행 시간과 예측 성능을 떨어뜨림

 

BOW 모델에서 피처 벡터화를 수행한다는 것모든 문서에서 모든 단어를 칼럼 형태로 나열하고 각 문서에서 해당 단어의 횟수나 정규화된 빈도를 값으로 부여하는 데이터 세트 모델로 변경하는 것

 

일반적으로 BOW의 피처 벡터화는 두 가지 방식

1. 카운트 기반의 벡터화

2. TF-IDF(Term Frequency - Inverse Document Frequency) 기반의 벡터화

 

카운트 기반의 벡터화

: 단어 피처에 값을 부여할 때 각 문서에서 해당 단어가 나타나는 횟수, 즉 count를 부여하는 경우, 카운트 값이 높을수록 중요한 단어로 인식됨, 그러나 카운트만 부여할 경우 그 문서의 특징을 나타내기보다는 언어의 특성상 문장에서 자주 사용될 수밖에 없는 단어까지 높은 값으로 부여하게 됨

 

TF-IDF

: 카운트 기반 단점을 보완하기 위해 등장한 것으로, 개별 문서에서 자주 나타나는 단어에 높은 가중치를 주되, 모든 문서에서 전반적으로 나타나는 단어에 대해서는 페널티를 주는 방식으로 값을 부여

단어에 대한 가중치의 균형을 맞추는 것, 문서마다 텍스트가 길고 문서의 개수가 많은 경우 카운트 방식보다 TF-IDF 방식을 사용하는 것이 더 좋은 예측 성능을 보장할 수 있음

 

 

사이킷런의 Count 및 TF-IDF 벡터화 구현 : CounterVectorizer, TfidfVectorizer

카운트 기반의 CountVectorizer은 단지 피처 벡터화만 수행하지 않으며 소문자 일괄 변환, 토큰화, 스톱 워드 필터링 등의 텍스트 전처리도 함께 수행

 

 

CounterVectorizer를 이용한 피처 벡터화 방법

1. 영어의 경우 모든 문자를 소문자로 변경(Default로 lowercase=True)

2. 디폴트로 단어 기준으로 n_gram_range를 반영해 각 단어 토큰화

3. 텍스트 정규화(StopWords 필터링만 수행, stemmer과 lemmatize는 countvectorizer 자체에서 지원 안됨, 이를 위해서는 함수 만들거나 외부 패키지로 미리 text normalize 수행 필요)

4. 파라미터를 이용하여 토큰화된 단어를 피처로 추출하고 단어 빈도수 벡터 값 적용

-> TF-IDF 벡터화는 TfidVectorizer 클래스를 이용하며 파라미터 변환 방법은 CounterVectorizer와 동일

 

 

BOW 벡터화를 위한 희소 행렬

조금 더 난이도가 있는 ML 모델을 수립하기 위해서는 희소 행렬이 어떤 형태로 되어 있는지 알아야 한다

 

BOW형태를 가진 언어 모델의 피러 벡터화는 대부분 희소 행렬이다

희소행렬은 너무 많은 불필요한 0값으로 인해 메모리 공간이 많이 필요하며, 행렬의 크기가 커서 연산 시에도 데이터 액세스를 위한 시간이 많이 소모된다

이러한 희소행렬을 물리적으로 적은 메모리 공간을 차지할 수 있도록 변환해야함

대표적인 방법으로 COO 형식과 CSR 형식이 존재, 일반적으로 CSR 형식이 더 뛰어남

 

희소행렬 - COO형식

0이 아닌 데이터만 별도의 데이터 배열(Array)에 저장하고, 그 데이터가 가리키는 행과 열의 위치를 별도의 배열로 저장하는 방식

 

희소행렬 - CSR형식(compressed sparse row)

COO 형식이 행과 열의 위치를 나타내기 위해서 반복적인 위치 데이터를 사용해야 하는 문제점을 해결한 방식

행 위치 배열 내에 있는 고유한 값의 시작 위치만 다시 별도의 위치 배열로 가지는 변환 방식

 

CSR로 변환하고 맨 마지막에는 데이터의 총 항목 개수를 배열에 추가

-> 고유 값의 시작 위치만 알고 있으면 얼마든지 행 위치 배열을 다시 만들 수 있기에 COO 방식보다 메모리가 적게 들고 빠른 연산이 가능하다

from scipy import sparse

coo = sparse.coo_matrix(dense1)
csr = sparse.csr_matrix(dense1)

사이키선의 CountVectorizer나 TfidfVectorizer 클래스로 변환된 피처 벡터화 행렬은 모두 사이파이의 CSR 형태의 희소행렬이다