TF-IDF로 단어 벡터화, k-fold로 교차검증하기

튜토리얼 파트 4 번외

  • tf-idf를 통해 벡터화 해보고 k_fold 를 사용해서 cross_validation 을 해본다.
import pandas as pd
"""
header = 0 은 파일의 첫 번째 줄에 열 이름이 있음을 나타내며 
delimiter = \t 는 필드가 탭으로 구분되는 것을 의미한다.
quoting = 3은 쌍따옴표를 무시하도록 한다.
"""
# QUOTE_MINIMAL (0), QUOTE_ALL (1), 
# QUOTE_NONNUMERIC (2) or QUOTE_NONE (3).

# 레이블인 sentiment 가 있는 학습 데이터
train = pd.read_csv('data/labeledTrainData.tsv', 
                    header=0, delimiter='\t', quoting=3)
# 레이블이 없는 테스트 데이터
test = pd.read_csv('data/testData.tsv', 
                   header=0, delimiter='\t', quoting=3)
print(train.shape)
print(test.shape)

(25000, 3)
(25000, 2)

# 긍정과 부정 리뷰가 어떻게 들어있는지 카운트 해본다.
# 긍정과 부정리뷰가 각각 동일하게 12,500개씩 들어있다.
train['sentiment'].value_counts()

1 12500
0 12500
Name: sentiment, dtype: int64

# 학습데이터에는 sentiment 데이터가 있다.
train.info()


RangeIndex: 25000 entries, 0 to 24999
Data columns (total 3 columns):
id 25000 non-null object
sentiment 25000 non-null int64
review 25000 non-null object
dtypes: int64(1), object(2)
memory usage: 586.0+ KB

# 테스트 데이터에는 sentiment가 없으며 이 sentiment를 예측하는 게 이 경진대회의 미션이다.
test.info()


RangeIndex: 25000 entries, 0 to 24999
Data columns (total 2 columns):
id 25000 non-null object
review 25000 non-null object
dtypes: object(2)
memory usage: 390.7+ KB

# 데이터 전처리는 튜토리얼 파트1~4까지 공통으로 사용되기 때문에 별도의 파이썬 파일로 분리했다.
# 그리고 캐글에 있는 코드를 병렬처리하도록 멀티프로세싱 코드를 추가했다.
# 하지만 여기에서는 멀티프로세싱 코드만 임포트해서 사용하고 전처리는 태그만 제거해주도록 한다.
# 그리고 WordNetLemmatizer로 레마타이징 한다.
# 음소표기법(lemmatization)은 튜토리얼 파트1에 설명 되어 있고 다음 링크에 있다.
# https://github.com/corazzon/KaggleStruggle/blob/master/word2vec-nlp-tutorial/tutorial-part-1.ipynb
from KaggleWord2VecUtility import KaggleWord2VecUtility
from bs4 import BeautifulSoup
from nltk.stem import WordNetLemmatizer
wordnet_lemmatizer = WordNetLemmatizer()

def review_to_words( raw_review ):
    review_text = BeautifulSoup(raw_review, 'html.parser').get_text()
    review_text = wordnet_lemmatizer.lemmatize(review_text)
    return review_text 
# 학습데이터를 전처리 한다.
%time train['review_clean'] = KaggleWord2VecUtility.apply_by_multiprocessing(\
    train['review'], review_to_words, workers=4)

CPU times: user 118 ms, sys: 130 ms, total: 248 ms
Wall time: 6.65 s

# 학습데이터와 동일하게 테스트 데이터에 대해서도 전처리 한다.
%time test['review_clean'] = KaggleWord2VecUtility.apply_by_multiprocessing(\
    test['review'], review_to_words, workers=4)

CPU times: user 132 ms, sys: 189 ms, total: 321 ms
Wall time: 6.71 s

# 전처리한 학습 데이터 10개만을 불러와서 본다.
train['review_clean'][:10]

0 With all this stuff going down at the moment ...
1
\The Classic War of the Worlds\ by Timothy ...
2 The film starts with a manager (Nicholas Bell...
3
It must be assumed that those who praised thi...
4 Superbly trashy and wondrously unpretentious ...
5
I dont know why people think this is such a b...
6 This movie could have been very good, but com...
7
I watched this video at a friend's house. I'm...
8 A friend of mine bought this film for £1, and...
9
This movie is full of references. Like \"Mad ...
Name: review_clean, dtype: object

# 전처리한 테스트 데이터 10개만을 불러와서 본다.
test['review_clean'][:10]

0 Naturally in a film who's main themes are of ...
1
This movie is a disaster within a disaster fi...
2 All in all, this is a movie for kids. We saw ...
3
Afraid of the Dark left me with the impressio...
4 A very accurate depiction of small time mob l...
5
...as valuable as King Tut's tomb! (OK, maybe...
6 This has to be one of the biggest misfires ev...
7
This is one of those movies I watched, and wo...
8 The worst movie i've seen in years (and i've ...
9
Five medical students (Kevin Bacon, David Lab...
Name: review_clean, dtype: object

# X_train과 X_test에 리뷰 데이터를 담아주고 이 데이터를 TF-IDF를 통해 임베딩(벡터화)해본다. 
X_train = train['review_clean']
X_test = test['review_clean']

TF-IDF

TF(단어 빈도, term frequency)는 특정한 단어가 문서 내에 얼마나 자주 등장하는지를 나타내는 값으로, 이 값이 높을수록 문서에서 중요하다고 생각할 수 있다. 하지만 단어 자체가 문서군 내에서 자주 사용되는 경우, 이것은 그 단어가 흔하게 등장한다는 것을 의미한다. 이것을 DF(문서 빈도, document frequency)라고 하며, 이 값의 역수를 IDF(역문서 빈도, inverse document frequency)라고 한다. TF-IDF는 TF와 IDF를 곱한 값이다.

IDF 값은 문서군의 성격에 따라 결정된다. 예를 들어 '원자'라는 낱말은 일반적인 문서들 사이에서는 잘 나오지 않기 때문에 IDF 값이 높아지고 문서의 핵심어가 될 수 있지만, 원자에 대한 문서를 모아놓은 문서군의 경우 이 낱말은 상투어가 되어 각 문서들을 세분화하여 구분할 수 있는 다른 낱말들이 높은 가중치를 얻게 된다.

역문서 빈도(IDF)는 한 단어가 문서 집합 전체에서 얼마나 공통적으로 나타나는지를 나타내는 값이다. 전체 문서의 수를 해당 단어를 포함한 문서의 수로 나눈 뒤 로그를 취하여 얻을 수 있다.

\begin{equation*}
\text{tfidf}(w, d) = \text{tf} \times (\log\big(\frac{N + 1}{N_w + 1}\big) + 1)
\end{equation*}

from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.pipeline import Pipeline
from nltk.corpus import words

vectorizer = CountVectorizer(analyzer = 'word', 
                             lowercase = True,
                             tokenizer = None,
                             preprocessor = None,
                             stop_words = 'english',
                             min_df = 2, # 토큰이 나타날 최소 문서 개수로 오타나 자주 나오지 않는 특수한 전문용어 제거에 좋다. 
                             ngram_range=(1, 3),
                             vocabulary = set(words.words()), # nltk의 words를 사용하거나 문서 자체의 사전을 만들거나 선택한다. 
                             max_features = 90000
                            )
vectorizer

CountVectorizer(analyzer='word', binary=False, decode_error='strict',
dtype=, encoding='utf-8', input='content',
lowercase=True, max_df=1.0, max_features=90000, min_df=2,
ngram_range=(1, 3), preprocessor=None, stop_words='english',
strip_accents=None, token_pattern='(?u)\b\w\w+\b',
tokenizer=None,
vocabulary={'bromopicrin', 'preinhere', 'hypsilophodontid', 'subjectivistic', 'paleocyclic', 'severity', 'Puccinia', 'octadecane', 'brennage', 'kerykeion', 'preconsideration', 'postcanonical', 'catadioptrical', 'Neobeckia', 'Pantotheria', 'misotheism', 'gummage', 'halting', 'unmottled', 'phalangal',... 'litigious', 'vermicle', 'bonhomie', 'typhus', 'jocuma', 'believe', 'evangelistary', 'Podophyllum'})

TfidfTransformer()

  • norm='l2' 각 문서의 피처 벡터를 어떻게 벡터 정규화 할지 정한다.
    • L2 : 벡터의 각 원소의 제곱의 합이 1이 되도록 만드는 것이고 기본 값
    • L1 : 벡터의 각 원소의 절댓값의 합이 1이 되도록 크기를 조절
  • smooth_idf=False
    • 피처를 만들 때 0으로 나오는 항목에 대해 작은 값을 더해서(스무딩을 해서) 피처를 만들지 아니면 그냥 생성할지를 결정
  • sublinear_tf=False
  • use_idf=True
    • TF-IDF를 사용해 피처를 만들 것인지 아니면 단어 빈도 자체를 사용할 것인지 여부
pipeline = Pipeline([
    ('vect', vectorizer),
    ('tfidf', TfidfTransformer(smooth_idf = False)),
])  
pipeline

Pipeline(memory=None,
steps=[('vect', CountVectorizer(analyzer='word', binary=False, decode_error='strict',
dtype=, encoding='utf-8', input='content',
lowercase=True, max_df=1.0, max_features=90000, min_df=2,
ngram_range=(1, 3), preprocessor=None, stop_words='english',
...('tfidf', TfidfTransformer(norm='l2', smooth_idf=False, sublinear_tf=False,
use_idf=True))])

%time X_train_tfidf_vector = pipeline.fit_transform(X_train)

CPU times: user 10.2 s, sys: 119 ms, total: 10.3 s
Wall time: 10.4 s

/Users/corazzon/codes/jupyter/lib/python3.6/site-packages/sklearn/feature_extraction/text.py:1067: RuntimeWarning: divide by zero encountered in true_divide
idf = np.log(float(n_samples) / df) + 1.0

vocab = vectorizer.get_feature_names()
print(len(vocab))
vocab[:10]

235892

['A',
'Aani',
'Aaron',
'Aaronic',
'Aaronical',
'Aaronite',
'Aaronitic',
'Aaru',
'Ab',
'Ababdeh']

%time X_test_tfidf_vector = pipeline.fit_transform(X_test)

CPU times: user 9.58 s, sys: 93.8 ms, total: 9.67 s
Wall time: 9.72 s

/Users/corazzon/codes/jupyter/lib/python3.6/site-packages/sklearn/feature_extraction/text.py:1067: RuntimeWarning: divide by zero encountered in true_divide
idf = np.log(float(n_samples) / df) + 1.0

import numpy as np
dist = np.sum(X_train_tfidf_vector, axis=0)

for tag, count in zip(vocab, dist):
    print(count, tag)

pd.DataFrame(dist, columns=vocab)

[[0. 0. 0. ... 0. 0. 0.]] A

A Aani Aaron Aaronic Aaronical Aaronite Aaronitic Aaru Ab Ababdeh ... zymotechnical zymotechnics zymotechny zymotic zymotically zymotize zymotoxic zymurgy zythem zythum
0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

1 rows × 235892 columns

from sklearn.ensemble import RandomForestClassifier

# 랜덤포레스트 분류기를 사용
forest = RandomForestClassifier(
    n_estimators = 100, n_jobs = -1, random_state=2018)
forest

RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=-1,
oob_score=False, random_state=2018, verbose=0,
warm_start=False)

%time forest = forest.fit(X_train_tfidf_vector, train['sentiment'])

CPU times: user 4min 46s, sys: 2.24 s, total: 4min 48s
Wall time: 1min 25s

Cross Validation 교차 검증

  • 일반화 성능을 측정하기 위해 데이터를 여러 번 반복해서 나누고 여러 모델을 학습한다.
    image.png
    이미지 출처 : https://www.researchgate.net/figure/228403467_fig2_Figure-4-k-fold-cross-validation-scheme-example

  • KFold 교차검증

    • 데이터를 폴드라 부르는 비슷한 크기의 부분집합(n_splits)으로 나누고 각각의 폴드 정확도를 측정한다.
    • 첫 번째 폴드를 테스트 세트로 사용하고 나머지 폴드를 훈련세트로 사용하여 학습한다.
    • 나머지 훈련세트로 만들어진 세트의 정확도를 첫 번째 폴드로 평가한다.
    • 다음은 두 번째 폴드가 테스트 세트가 되고 나머지 폴드의 훈련세트를 두 번째 폴드로 정확도를 측정한다.
    • 이 과정을 마지막 폴드까지 반복한다.
    • 이렇게 훈련세트와 테스트세트로 나누는 N개의 분할마다 정확도를 측정하여 평균 값을 낸게 정확도가 된다.
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
k_fold = KFold(n_splits=5, shuffle=True, random_state=2018)

%time score = np.mean(cross_val_score(\
    forest, X_train_tfidf_vector, \
    train['sentiment'], cv=k_fold, scoring='roc_auc', n_jobs=-1))

CPU times: user 559 ms, sys: 213 ms, total: 772 ms
Wall time: 5min 41s

# 0.92149
'{:,.5f}'.format(score)

'0.92068'

%time result = forest.predict(X_test_tfidf_vector)

CPU times: user 4.97 s, sys: 138 ms, total: 5.11 s
Wall time: 1.78 s

result[:10]

array([1, 0, 0, 0, 1, 1, 0, 1, 0, 1])

output = pd.DataFrame(data={'id':test['id'], 'sentiment':result})
output.head()
id sentiment
0 12311\_10 1
1 8348\_2 0
2 5828\_4 0
3 7186\_2 0
4 12128\_7 1
output_sentiment = output['sentiment'].value_counts()
print(output_sentiment[0] - output_sentiment[1])
output_sentiment

-376

1 12688
0 12312
Name: sentiment, dtype: int64

output.to_csv('data/tutorial_4_tfidf_{0:.5f}.csv'.format(score), index=False, quoting=3)
# 그런데 점수가 너무 낮게 나옴
# local 0.92149 kaggle 0.84392
# stop_words = 'english' vocabulary = set(words.words()), smooth_idf = False
468/578

0.8096885813148789

강의에 등록된 질문이 없습니다. 궁금한 부분이 있으면 주저하지 말고 무엇이든 물어보세요.