NLP

NLP 실습 텍스트 분류(TF-IDF, CountVectorizer, Word2Vec) -02

모델링 소개

  • 선형모델

    • 로지스틱회귀 모델
      • 입력 벡터를 word2vec과 tf-idf를 사용해본다.
  • 랜던포레스트

TF-IDF를 활용한 모델 구현

  • 모델의 입력값으로 TF-IDF 값을 갖는 벡터를 사용할 것이기 때문에 scikit-learn의 TfidfVectorizer를 사용할 것이다. 이를 위해서는 입력값이 텍스트로 이뤄진 데이터 형태이어야 한다.
1
2
3
train_data = pd.read_csv('train_clean.csv')
reviews = list(train_data['clean_review'])
sentiments = list(train_data['sentiment'])

TF-IDF Vectorizing

  • 데이터에 대해 TF-IDF 값으로 벡터화를 진행한다.
    • min_df : 설정한 값보다 특정 Token의 df 값이 더 적게 나오면 벡터화 과정에서 제거
    • anlayzer : 분석 단위를 의미, ‘word’의 경우 간어 하나를 단위로, ‘char’는 문자 하나를 단위로
    • sublinear_tf : 문서의 단어 빈도수(tf:term frequency)에 대한 smoothing 여부를 설정
    • ngram_range : 빈도의 기본 단위를 어떤 범위의 n-gram으로 설정할 것인지를 보는 인자
    • max_features : 각 벡터의 최대 길이(특징의 길이)를 설정
1
2
3
4
5
6
from sklearn.feature_extraction.text import TfidfVectorizer

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

X = vectorizer.fit_transform(reviews)
X
1
train_data.shape

학습과 검증 데이터셋 분리

1
2
3
4
5
6
7
8
9
from sklearn.model_selection import train_test_split
import numpy as np

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)
  • class_wight=’balanced’로 설정해서 각 label에 대해 균형 있게 학습할 수 있게 한 것이다.
1
2
3
4
from sklearn.linear_model import LogisticRegression

lgs = LogisticRegression(class_weight = 'balanced')
lgs.fit(X_train, y_train)
1
print("Accuracy: {}".format(lgs.score(X_eval, y_eval)))
  • 필자는 Accuracy: 0.8676을 출력으로 받았다. validation data에 대한 성능이 약 87%의 정확도를 갖으므로 test data에 대해서도 비슷한 수준일 것이라고 기대하며 kaggle에 test data의 예측값을 제출해 볼 것이다.

데이터 제출하기

  • 만든 모델을 활용해 평가 데이터 결과를 예측하고 캐글에 제출할 수 있도록 파일로 저장할 것이다.
1
2
3
4
5
6
test_data = pd.read_csv('test_clean.csv')

testDataVecs = vectorizer.transform(test_data["review"])

test_predicted = lgs.predict(testDataVecs)
print(test_predicted)
1
2
3
4
5
6
if not os.path.exists(DATA_OUT_PATH):
os.makedirs(DATA_OUT_PATH)

ids = list(test_data['id'])
answer_dataset = pd.DataFrame({'id' : ids, "sentiment" : test_predicted})
answer_dataset.to_csv(DATA_OUT_PATH + 'lgs_tfidf_answer.csv', index=False, quoting=3)
1
!kaggle competitions submit word2vec-nlp-tutorial -f "lgs_tfidf_answer.csv" -m "LogisticRegression Model with tf-idf"

TfidfVectorizer를 사용한 LogisticRegression모델의 test data 정확도

Woed2vec(CBOW)을 활용한 모델 구현

  • 이번에는 word2vec을 활용해 모델을 구현할 것이다. 우선 각 단어에 대해 word2vec으로 벡터화해야 한다. word2vec의 경우 단어로 표현된 리스트를 입력값으로 넣어야 하기 때문에 전처리한 넘파이 배열을 바로 사용하지 않는다. 따라서 전처리된 텍스트 데이터를 불러온 후 각 단어들의 리스트로 나눠야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
DATA_IN_PATH = "/content/"

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'])

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

word2ve 벡터화

  • num_features : 각 단어에 대해 임베딩된 벡터의 차원을 정한다.
  • min_word_count : 모델에 의미 있는 단어를 가지고 학습하기 위해 적은 빈도 수의 단어들은 학습 하지 않기 위해 최소 빈도수를 설정한다.
  • num_workers : 모델 학습 시 학습을 위한 프로세스 개수를 지정한다.
  • context : word2vec을 수행하기 위한 context 윈도우 크기를 지정한다.
  • downsampling : word2vec 학습을 수행할 때 빠른 학습을 위해 정답 단어 label에 대한 downsampling 비율을 지정한다. 보통 0.001이 좋은 성능을 낸다고 한다.

참고로 parameter 중에 sg의 default값인 0을 사용했으므로 이 모델은 Word2vec의 CBOW모델이다.

1
2
3
4
5
num_features = 300
min_word_count = 40
num_workers = 4
context = 10
downsampling = 1e-3
1
!pip install gensim
  • word2vec을 학습하는 과정에서 진행 상황을 확인해 보기 위해 다음과 같이 logging을 통해 확인해 볼 수 있다.

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

1
2
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
1
2
3
4
from gensim.models import word2vec
print("Training model ....")

model = word2vec.Word2Vec(sentences, workers=num_workers, size=num_features, min_count=min_word_count, window=context, sample=downsampling)
  • word2vec으로 학습시킨 모델의 경우 모델을 따로 저장해두면 이후에 다시 사용할 수 있기 때문에 저장해 두고 이후에 학습한 값이 추가로 필요할 경우 사용하면 된다.
1
2
3
4
5
# 모델의 하이퍼파라미터를 설정한 내용을 모델 이름에 담는다면 나중에 참고하기에 좋다.
# 모델을 저장하면 Word2Vec.load()를 통해 모델을 다시 사용할 수 있다.

model_name = "300features_40minwords_10context"
model.save(model_name)
  • word2vec 모델을 활용해서 선형 회귀 모델을 학습할 것이다. 우선 학습을 하기 위해서는 하나의 review를 같은 형태의 입력값으로 만들어야 한다. 지금은 word2vec 모델에서 각 단어가 벡터로 표현되어 있다. 그리고 review 마다 단어의 개수가 모두 다르기 때문에 입력값을 하나의 형태로 만들어야 한다.

  • 아래 model을 통해 얻은 단어 하나의 feature는 (300,)의 shape를 갖게 될 것이다.

  • 가장 단순한 방법은 문장에 있는 모든 단어의 벡터값에 대해 평균을 내서 리뷰 하나당 하나의 벡터로 만드는 방법이 있다.

    • words : 단어의 모음인 하나의 review
    • model : 학습한 word2vec 모델
    • num_features : word2vec으로 임베딩할 때 정했던 벡터의 차원 수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_features(words, model, num_features):
# 출력 벡터 초기화
feature_vector = np.zeros((num_features), dtype=np.float32)

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

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

# 문장의 단어 수만큼 나누어 단어 벡터의 평균값을 문장 벡터로 함
feature_vector = np.divide(feature_vector, num_words)
return feature_vector
1
2
3
4
5
6
7
8
9
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
1
train_data_vecs = get_dataset(sentences, model, num_features)

학습과 검증 데이터셋 분리

1
2
3
4
5
6
7
8
9
10
from sklearn.model_selection import train_test_split
import numpy as np

X = train_data_vecs
y = np.array(sentiments)

RANDOM_SEED = 42
TEST_SPLIT = 0.2

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

모델 선언 및 학습

1
2
3
4
from sklearn.linear_model import LogisticRegression

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

검증 데이터셋을 이용한 성능 평가

  • 이전의 TF-IDF를 사용해서 학습한 것보단 상대적으로 성능이 떨어진다. word2vec이 단어 간의 유사도를 보는 관점에서는 분명히 효과적일 수는 있지만 word2vec을 사용하는 것이 항상 가장 좋은 성능을 보장하지는 않는다는 것을 다시 한번 알 수 있다!!!
1
print("Accuracy: %f" % lgs.score(X_eval, y_eval))
  • validation data에 대한 정확도는 83%정도로 TF-IDF로 했던 것보단 조금 떨어지지만 캐글에 제출해보고 overfitting이 발생했는지 점검해 본다.
1
2
3
4
5
TEST_CLEAN_DATA = 'test_clean.csv'

test_data = pd.read_csv(DATA_IN_PATH + TEST_CLEAN_DATA)

test_review = list(test_data['review'])
1
2
3
test_sentences = []
for review in test_review:
test_sentences.append(review.split())
1
test_data_vecs = get_dataset(test_sentences, model, num_features)
1
2
3
4
5
6
7
8
9
DATA_OUT_PATH = '/content/'

test_predicted = lgs.predict(test_data_vecs)

if not os.path.exists(DATA_OUT_PATH):
os.makedirs(DATA_OUT_PATH)

test_data['id']=test_data['id'].apply(lambda x : x[1:-1])
ids = list(test_data['id'])
1
2
answer_dataset = pd.DataFrame({'id': ids, 'sentiment': test_predicted})
answer_dataset.to_csv(DATA_OUT_PATH + 'lgs_answer.csv', index=False)
1
!kaggle competitions submit word2vec-nlp-tutorial -f "lgs_answer.csv" -m "LogisticRegression Model with Word2vec"

Word2vec Vectorizing을 사용한 LogisticRegression

랜덤포레스트 분류 모델

CountVectorizer를 활용한 벡터화

  • CountVectorizer는 TF-IDF vectorizing과 동일하게 문장을 input으로 받기 때문에 Word2vec처럼 공백단위로 쪼개 단어로 사용하지 않을 것이다.
1
2
3
4
5
6
7
8
import pandas as pd

DATA_IN_PATH = '/content/'
TRAIN_CLEAN_DATA = 'train_clean.csv'

train_data = pd.read_csv(DATA_IN_PATH + TRAIN_CLEAN_DATA)
reviews = list(train_data['clean_review'])
y = np.array(train_data['sentiment'])
1
2
3
4
5
from sklearn.feature_extraction.text import CountVectorizer

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

train_data_features = vectorizer.fit_transform(reviews)

학습과 검증 데이터 분리

1
2
3
4
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)

모델 구현 및 학습

1
2
3
4
5
6
7
from sklearn.ensemble import RandomForestClassifier

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

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

검증 데이터셋으로 성능 평가

  • 결과를 보면 대략 85%의 정확도를 보여준다. 앙상블 모델인데도 앞서 사용한 간단한 모델(TF_IDF보단 상대적으로)보다 좋지 않은 성능을 보여준다. 이는 모델의 문제일 수도 있고 데이터에서 특징을 추출하는 방법의 문제일 수도 있다. 즉, 모델을 바꾸지 않더라도 특징 추출 방법을 앞서 사용한 TF-IDF나 word2vec을 사용해서 입력값을 만든다면 성능이 높아질 수 있다.
1
print("Accuracy: %f" % forest.score(eval_input, eval_label))

데이터 제출

1
2
3
4
5
6
7
TEST_CLEAN_DATA = 'test_clean.csv'
DATA_OUT_PATH = '/content/'

test_data = pd.read_csv(DATA_OUT_PATH + TEST_CLEAN_DATA)

test_reviews = list(test_data['review'])
ids = list(test_data['id'])
1
test_data_features = vectorizer.transform(test_reviews)
1
2
3
4
5
6
7
8
if not os.path.exists(DATA_OUT_PATH):
os.makedirs(DATA_OUT_PATH)

result = forest.predict(test_data_features)

output = pd.DataFrame({'id': ids, "sentiment": result})

output.to_csv(DATA_OUT_PATH + 'Randomforest_model_with_Countvectorizer.csv', index=False, quoting=3)
1
!kaggle competitions submit word2vec-nlp-tutorial -f "Randomforest_model_with_Countvectorizer.csv" -m "Randomforest Model with Countvectorizer"

Count vectorizing을 사용한 Random Forest 성능