본문 바로가기
정리/텐서플로와 머신러닝으로 시작하는 자연어처리

Chap04. 영어 텍스트 분류_모델링 1

by 스꼬맹이브로 2021. 7. 26.
728x90
반응형
SMALL

이전 포스팅에서는 탐색적 데이터 분석 과정과 데이터 전처리 과정에 대해 알아보았다.

 

Chap04. 텍스트 분류_데이터전처리 실습

텍스트 분류란 자연어 처리 기술을 활용해 글의 정보를 추출해서 문제에 맞게 사람이 정한 범주(Class)로 분류하는 문제다. 텍스트 분류의 방법과 예시 등 이론적인 내용은 앞에서 살펴봤으므로

yuna96.tistory.com

오늘은 이어서 직접 모델에 적용하고 텍스트의 감정이 긍정인지 부정인지를 예측할 수 있는 모델을 만들어보자.

실습할 모델은 로지스틱 회귀 모델과 랜덤 포레스트 모델이다.

 

1. 로지스틱 회귀 모델

로지스틱 회귀 모델은 주로 이항 분류를 하기 위해 사용되며 분류 문제에서 사용할 수 있는 가장 간단한 모델이다.

로지스틱 회귀는 선형 결합을 통해 나온 결과를 토대로 예측하기 때문에 선형 회귀 모델 먼저 살펴보자.

 

▶ 선형 회귀 모델

선형 회귀 모델은 종속변수와 독립변수 간의 상관관계를 모델링하는 방법이다.

즉, 하나의 선형 방정식으로 표현해 예측할 데이터를 분류하는 모델이라고 할 수 있다.

선형 회귀 모델

다음과 같이 하나의 선으로 데이터를 분류하는 것을 말하며 선형 회귀 모델의 식은 다음과 같다.

선형 회귀 모델 수식

w와 b는 학습하고자 하는 파라미터를 나타내며, x는 입력값이다.

즉, x에 주로 단어 또는 문장 표현 벡터를 입력하게 된다.

더불어 변수 x로 다루지 않고 벡터 x로 다루게 된다.

 

▶ 로지스틱 회귀 모델

로지스틱 모델은 선형 모델의 결괏값에 로지스틱 함수를 적용해 0~1 사이의 값을 갖게 해서 확률로 표현한다.

이렇게 나온 결과를 통해 1에 가까우면 정답은 1이고, 0에 가까우면 정답은 0이라 예측한다.

이번 모델에서는 입력값은 단어를 word2vec를 통한 임베딩 방법과 tf-idf를 통한 임베딩 방법 두가지를 사용해보자.

 

1. tf-idf 활용한 모델 구현

import os

import pandas as pd
import numpy as np

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

DATA_IN_PATH = './data_in/' 
DATA_OUT_PATH = './data_out/'
TRAIN_CLEAN_DATA = 'train_clean.csv'

train_data = pd.read_csv( DATA_IN_PATH + TRAIN_CLEAN_DATA )

reviews = list(train_data['review'])
sentiments = list(train_data['sentiment'])

먼저 각종 필요한 라이브러리와 데이터를 불러온다.

판다스를 사용하여 데이터를 불러온 후 리뷰와 레이블은 따로 리스트로 지정해둔다.

다음으로 데이터에 대해 tf-idf 값으로 벡터화를 진행한다.

vectorizer = TfidfVectorizer(min_df = 0.0, analyzer="char", sublinear_tf=True, ngram_range=(1,3), max_features=5000) 

X = vectorizer.fit_transform(reviews)

여기서 몇 가지 인자값을 추가로 설정해주었으며, 각각의 의미는 다음과 같다.

  • min_df : 설정한 값보다 특정 토큰의 df값이 더 적게 나오면 벡터화 과정에서 제거한다.
  • analyzer : 분석하기 위한 기준 단위. 'word'(단어 기준)와 'char'(문자기준)로 2가지 옵션을 제공
  • sublinear_tf : 문서의 단어 빈도 수(term frequency)에 대한 스무딩(smoothing)여부를 설정
  • ngram_range : 단어 묶음 범위 설정
  • max_features : 벡터의 최대 길이 설정

위의 코드로 인해 간단하게 tf-idf 벡터화한 데이터가 준비되었다.

이제 학습데이터와 검증 데이터를 분리해보자.

RANDOM_SEED = 42
TEST_SPLIT = 0.2

y = np.array(sentiments)

X_train, X_eval, y_train, y_eval = train_test_split(X, y, test_size=TEST_SPLIT, random_state=RANDOM_SEED)

입력 값인 X와 정답 레이블을 넘파이 베열로 만든 y를 적용하고, 20퍼센트의 비율을 설정하여 8:2로 학습 데이터와 검증 데이터로 나눴다.

이제 학습 데이터를 모델에 적용해 보자.

lgs = LogisticRegression(class_weight='balanced') 
lgs.fit(X_train, y_train)

사이킷런 라이브러리에서 로지스틱 회귀 모델을 제공하기 때문에 객체를 생성해 fit 함수를 호출하면 모델 학습이 진행된다.

여기서 특별한 점은 모델 생성 시 class_weight를 balanced로 설정해서 각 레이블에 대해 균형 있게 학습하도록 하였다.

이제 정의한 모델에 검증 데이터를 사용하여 성능을 측정해보자.

print("Accuracy: %f" % lgs.score(X_eval, y_eval))

결과 :

Accuracy: 0.859800

 

결과는 정확도가 약 0.86으로 나쁘지 않은 결과가 나왔다.

이 성능은 앞으로 모델을 어떻게 튜닝해서 성능을 높일 수 있는지 볼 수 있는 하나의 기준 지표가 될 수 있다.

여기서는 성능 평가 방법으로 정확도만 확인했는데, 보통 정밀도(Precision), 재현율(Recall), f1-score, auc등 다양한 지표도 같이 확인한다.

 

1. word2vec 활용한 모델 구현

import os

import pandas as pd
import numpy as np

from bs4 import BeautifulSoup
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

DATA_IN_PATH = './data_in/' 
DATA_OUT_PATH = './data_out/'
TRAIN_CLEAN_DATA = 'train_clean.csv'

train_data = pd.read_csv( DATA_IN_PATH + TRAIN_CLEAN_DATA )

reviews = list(train_data['review'])
sentiments = list(train_data['sentiment'])

먼저 위와 같이 필요한 라이브러리와 데이터를 불러온다.

word2vec의 경우 단어로 표현된 리스트를 입력값으로 넣어야 하기 때문에 전처리한 넘파이 배열을 사용하지 않는다.

따라서 전처리된 텍스트 데이터를 불러온 후 각 단어들의 리스트로 나눠야 한다.

전처리된 텍스트는 하나의 문자열로 되어있기 때문에 각 문자열을 split함수를 사용해서 띄어쓰기를 기준으로 나눈다.

sentences = []
for review in reviews:
    sentences.append(review.split())

데이터 전처리가 끝났다면 word2vec모델 학습을 진행해 보자.

먼저 word2vec모델의 하이퍼파라미터를 설정한다.

num_features = 300    # 워드 벡터 특징값 수
min_word_count = 40   # 단어에 대한 최소 빈도 수
num_workers = 4       # 프로세스 개수
context = 10          # 컨텍스트 윈도우 크기
downsampling = 1e-3   # 다운 샘플링 비율

설정한 파라미터에 대한 설명은 다음과 같다.

  • num_features : 각 단어에 대해 임베딩된 벡터의 차원을 정함
  • min_word_count : 모델에 의미 있는 단어를 가지고 학습하기 위해 적은 빈도 수의 단어들은 배제함
  • num_workers : 학습을 위한 프로세스 개수 지정
  • context : 컨텍스트 윈도우 크기 지정
  • downsampling : 빠른 학습을 위해 정답 단어 레이블에 대한 다운샘플링 비율 지정(보통 0.001이 좋은 성능을 낸다고 함)

다음의 파라미터를 가지고 학습을 수행하자.

학습에는 gensim 라이브러리를 사용할 것이기 때문에 먼저 라이브러리를 설치 후 진행하자.

설치하는 명령어는 다음과 같다.

pip install gensim

다음으로 word2vec의 학습 진행 사항을 확인하기 위해 logging을 만들어주자.

import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

로깅을 할 때 format을 위와 같이 지정하고, 로그 수준은 INFO에 맞추면 word2vec의 학습 과정에서 로그 메시지를 양식에 맞게 INFO수준으로 보여준다.

 

다음은 word2vec모델을 학습시키는 코드이다.

from gensim.models import word2vec

model = word2vec.Word2Vec(sentences, workers=num_workers, \
           vector_size=num_features, min_count = min_word_count, \
            window = context, sample = downsampling)

결과 :

word2vec학습을 위해서는 word2vec모듈에 있는 word2vec객체를 생성해서 실행한다.

이렇게 학습된 객체는 model 변수에 할당하여 사용한다.

이때 학습을 위한 객체의 인자는 입력할 데이터와 하이퍼파라미터를 순서대로 입력해야 원하는 하이퍼파라미터를 사용하여 학습할 수 있다.

 

이제 만들어진 모델을 활용해 선형 회귀 모델을 학습해보자.

학습을 위해서는 하나의 리뷰를 같은 형태의 입력값으로 만들어야 한다.

때문에 가장 단순하게 문장에 있는 모든 단어의 벡터값에 대해 평균을 내서 리뷰 하나당 하나의 벡터로 만들어보자.

다음 코드는 하나의 리뷰에 대해 전체 단어의 평균값을 계산하는 함수이다.

def get_features(words, model, num_features):
	# 출력 벡터 초기화
    feature_vector = np.zeros((num_features),dtype=np.float32)

    num_words = 0
    # 어휘 사전 준비
    index_to_key_set = set(model.wv.index_to_key)

    for w in words:
        if w in index_to_key_set:
            num_words += 1
            # 사전에 해당하는 단어에 대해 단어 벡터를 더함
            feature_vector = np.add(feature_vector, model.wv[w])

	# 문장에 단어 수만큼 나누어 단어 벡터의 평균값을 문장 벡터로 지정
    feature_vector = np.divide(feature_vector, num_words)
    return feature_vector

각 인자에 대한 설명은 다음과 같다.

  • words : 단어의 모음인 하나의 리뷰
  • model : word2vec모델
  • num_features : word2vec로 임베딩할 때 정했던 벡터의 차원 수

이제 평균값을 구하는 함수를 사용해 각 리뷰의 평균 벡터를 구하는 함수를 정의한다.

def get_dataset(reviews, model, num_features):
    dataset = list()

    for s in reviews:
        dataset.append(get_features(s, model, num_features))

    reviewFeatureVecs = np.stack(dataset)
    
    return reviewFeatureVecs

각 인자에 대한 설명은 다음과 같다.

  • reviews : 전체 리뷰 데이터
  • model : word2vec모델
  • num_features : word2vec로 임베딩할 때 정했던 벡터의 차원 수

이제 전체 리뷰 데이터를 위의 함수를 사용해 실제 학습에 사용될 입력 값을 만들자.

test_data_vecs = get_dataset(sentences, model, num_features)

데이터셋이 만들어졌다면 학습데이터와 검증데이터를 나누자.

이 방식은 위의 tf-idf와 동일하다.

from sklearn.model_selection import train_test_split
import numpy as np

RANDOM_SEED = 42
TEST_SPLIT = 0.2

X = test_data_vecs
y = np.array(sentiments)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SPLIT, random_state=RANDOM_SEED)

다음으로 로지스틱 모델을 사용해보자.

이 과정부터는 위의 tf-idf와 동일하므로 설명은 생략한다.

from sklearn.linear_model import LogisticRegression

lgs = LogisticRegression(class_weight='balanced')
lgs.fit(X_train, y_train)

predicted = lgs.predict(X_test)
from sklearn import metrics

fpr, tpr, _ = metrics.roc_curve(y_test, (lgs.predict_proba(X_test)[:, 1]))
auc = metrics.auc(fpr, tpr)

print("------------")
print("Accuracy: %f" % lgs.score(X_test, y_test))  #checking the accuracy
print("Precision: %f" % metrics.precision_score(y_test, predicted))
print("Recall: %f" % metrics.recall_score(y_test, predicted))
print("F1-Score: %f" % metrics.f1_score(y_test, predicted))
print("AUC: %f" % auc)

결과 :

------------

Accuracy: 0.865400

Precision: 0.859424

Recall: 0.876141

F1-Score: 0.867702

AUC: 0.937070

 

학습된 결과를 보면 tf-idf보다 상대적으로 성능이 조금 올라간 것을 볼 수 있다.

물론, word2vec가 무조건적으로 좋은 성능을 내는 것은 아니므로 비교하는 과정은 꼭 필요하다고 생각한다.

또한 정밀도, 재현율, f1-score, auc도 다음과 같이 확인할 수 있다.

 

▶ 랜덤 포레스트 모델

랜덤 포레스트는 분류 또는 회귀 등에 사용되는 앙상블 모델의 일종으로, 여러 개의 의사결정 트리의 결과값을 평균낸 것을 결과로 사용한다.

즉, 하나의 의사결정 트리를 사용했을 때보다 정확도가 높아지는 장점이 있다.

앞에서 tf-idf와 word2vec를 다뤄보았으니 이번에는 countvectorizer를 사용하여 구현해보자.

먼저 필요한 라이브러리와 데이터를 불러온다.

import pandas as pd
import numpy as np
import os
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split

DATA_IN_PATH = './data_in/'
DATA_OUT_PATH = './data_out/'
TRAIN_CLEAN_DATA = 'train_clean.csv'

train_data = pd.read_csv(DATA_IN_PATH + TRAIN_CLEAN_DATA)

reviews = list(train_data['review'])
y = np.array(train_data['sentiment'])

이제 불러온 텍스트 데이터를 벡터화한다.

vectorizer = CountVectorizer(analyzer = "word", max_features = 5000) 

train_data_features = vectorizer.fit_transform(reviews)

countvectorizer객체를 생성할 때 인자 값을 설정하는데 분석 단위를 하나의 단어로 지정하기 위해 analyzer는 word로 설정하고 각 벡터의 최대 길이를 5000으로 설정하였다.

 

다음으로 학습 데이터와 검증 데이터를 분리하자.

TEST_SIZE = 0.2
RANDOM_SEED = 42

train_input, eval_input, train_label, eval_label = train_test_split(train_data_features, y, test_size=TEST_SIZE, random_state=RANDOM_SEED)

위와 동일하게 8:2로 분리를 완료하였다.

 

이제 모델을 구현하고 학습해보자.

랜덤 포레스트는 사이킷런 라이브러리의 RandomForestClassifier 객체를 사용해 구현한다.

from sklearn.ensemble import RandomForestClassifier


# 랜덤 포레스트 분류기에  100개 의사 결정 트리를 사용한다.
forest = RandomForestClassifier(n_estimators = 100) 

# 단어 묶음을 벡터화한 데이터와 정답 데이터를 가지고 학습을 시작한다.
forest.fit( train_input, train_label )

객체 생성 시 인자를 설정하는데, 이는 트리의 개수를 의미한다. 

본 코드에서는 100으로 설정하였으므로 의사결정 트리를 100개를 만들어서 최종 결과를 만들어 낸다.

이렇게 생성한 모델에 학습 데이터와 레이블을 적용해 학습시킨다.

 

학습이 다 되었으면 검증 데이터를 사용해 성능을 평가해보자.

print("Accuracy: %f" % forest.score(eval_input, eval_label))

결과 :

Accuracy: 0.846400

 

성능은 모델의 score함수를 사용해 측정하였으며, 약 85% 정확도를 보여주었다.

앙상블 모델인데도 앞서 사용한 간단한 모델보다 상대적으로 좋지 않은 것을 보여준다.

이는 모델의 문제일 수도 있고 데이터 특징 추출 문제일 수도 있다.

즉, 벡터화하는 방법을 바꾼다면 성능이 좋아질 수도 있다.

 

이번 모델까지는 머신러닝을 활용한 모델들이었으며, 다음 포스팅부터 딥러닝을 활용해 분류하는 모델에 대하여 알아보자.

 

 

728x90
반응형
LIST