이번 주제는 Spooky Author Identification 이다.
공포이야기가 쓰여진 책의 문장의 단어를 분석하여 작가를 예측하는 모델을 구현 한다.
제출은 id(문장에대한 고유한 id) 별로 3명의 작가에 대한 각각의 확률을 구한다.
id, EAP, HPL, MWS
id07943,0.33,0.33,0.33
...
Abhishek Thakur님의 Approaching (Almost) Any NLP Problem on Kaggle 를 참고하여 자연어 분석어를 진행하였다.
이제 차근차근 따라가 봅시다.
1. 데이터 준비
import pandas as pd
import numpy as np
import xgboost as xgb
from tqdm import tqdm
from sklearn.svm import SVC
from keras.models import Sequential
from keras.layers.recurrent import LSTM, GRU
from keras.layers.core import Dense, Activation, Dropout
from keras.layers.embeddings import Embedding
from keras.layers.normalization import BatchNormalization
from keras.utils import np_utils
from sklearn import preprocessing, decomposition, model_selection, metrics, pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from keras.layers import GlobalAveragePooling1D, Conv1D, MaxPooling1D, Flatten, Bidirectional, SpatialDropout1D
from keras.preprocessing import sequence, text
from keras.callbacks import EarlyStopping
from nltk import word_tokenize
import nltk
nltk.download('stopwords')
stop_words = stopwords.words('english')
from nltk.corpus import stopwords
train = pd.read_csv('../data/spooky-author-identification/train/train.csv')
test = pd.read_csv('../data/spooky-author-identification/test/test.csv')
sample = pd.read_csv('../data/spooky-author-identification/sample_submission/sample_submission.csv')
train.head()
test.head()
sample.head()
우리의 목표는 text를 분석하여 EAP, HPL, MWS와 같은 저자를 예측해야 한다. 간단히 말해서, 3개의 다른 클래스로 텍스트 분류하는 것이다. 해결방법은 캐글은 multi-class log-loss를 평가 지표를 지정하였다. 자세한 것은 해당 사이트 참고
def multiclass_logloss(actual, predicted, eps=1e-15):
"""
Multi class version of Logarithmic Loss metric.
:param actual: Array containing the actual target classes
:param prdicted: Matrix with class predictions, one probability per class
"""
if len (actual.shape) == 1: # 실제값이 하나라면 배열 다시 생성.
actual2 = np.zeros((actual.shape[0], predicted.shape[1]))
for i, val in enumerate(actual):
actual2[i, val] = 1
actual= actual2
clip = np.clip(predicted, eps, 1-eps)
rows = actual.shape[0]
vstoa = np.sum(actual * np.log(clip))
return -1.0 / rows * vstoa
# scikit learn의 LabelEncoder를 해보자. 0,1,2로 변환할 것이다.
lbl_enc = preprocessing.LabelEncoder()
y = lbl_enc.fit_transform(train.author.values)
y
# 그리고, 데이터셋을 training 데이터와 검증 데이터 셋으로 나눠준다. scikit-learn의 model_selection을 사용하여 나눠준다.
xtrain, xvalid, ytrain, yvalid = train_test_split(train.text.values, y, stratify=y,
random_state=42, test_size=0.1, shuffle=True)
print( xtrain.shape)
print( xvalid.shape)
print( ytrain.shape)
print( yvalid.shape)
2. 자연어 모델 구현
2.1 TF-IDF
첫번째 모델은 TF-IDF(Term Frequency - Inverse Document Frequency)이다. 이것은 간단한 Logistic Regression을 따른다.
# 아래와 같은 feature들로 대부분 시작한다.
# TfidfVectorizer : 단어 카운트 가중치
# min_df : DF(document-frequency)의 최소 빈도값 설정, DF는 특정 단어가 나타나는 '문서의 수'를 의미,단어의 수가 아니라!, 3이면 1,2인 것들을 탈락함
# analyzer : 'word', 'char' 중 선택
# sublinear_tf : TF(Term-Freqeuncy,단어빈도) 값의 스무딩 여부를 결정하는 파라미터 T/F , 높은 TF값을 완만하게 처리하는 효과. 아웃라이어가 너무 심한 경우 사용
# ngram_range : 단어의 묶음, ex) very good은 두 단어가 묶여야 정확한 의미가 살아난다.
# max_features : tf-idf vector의 최대 feature를 설정해주는 것, tf-idf 벡터는 단어사전의 인덱스만큼 feature를 부여받음. 종류의 숫자를 제한.
tfv = TfidfVectorizer(min_df=3, max_features=None, strip_accents='unicode', analyzer='word',
token_pattern=r'\w{1,}', ngram_range=(1,3), use_idf=1, smooth_idf=1, sublinear_tf=1,
stop_words='english')
# training, test set 둘다 TF-IDF 로 fit하기, 벡터라이저가 단어들을 학습시킨다.
tfv.fit(list(xtrain) + list(xvalid))
tfv.vocabulary_ # 벡터라이저가 학습한 단어사전을 출력한다.
xtrain_tfv = tfv.transform(xtrain)
xvalid_tfv = tfv.transform(xvalid)
2.2 Logistic Regression
# TF IDF로 간단한 Logistic Regression Fit하기.
clf = LogisticRegression(C=1.0)
clf.fit(xtrain_tfv, ytrain)
predictions = clf.predict_proba(xvalid_tfv)
print("logloss: %0.3f " %multiclass_logloss(yvalid, predictions))
우리는 multiclass logloss로 0.572의 손실을 얻었다.
2.3 counter 변수 사용.
- 더 좋은 점수를 얻기 위해 다른 데이터로 동일한 모델을 적용해본다.
- TF-IDF를 사용하기 전에, 우리는 feature로써 word count를 사용할 수 있다.
- 이것은 scikit-learn의 CountVectorizer로 쉽게 사용할 수 있다.
# CountVectorizer : 문서 집합에서 단어 토큰을 생성하고 각 단어의 수를 세어 BOW 인코딩 벡터를 만든다.
ctv = CountVectorizer(analyzer='word', token_pattern= r'\w{1,}', ngram_range=(1,3), stop_words='english')
# training, test 데이터셋 모두 fit count vectorizer fit하자.
ctv.fit(list(xtrain) + list(xvalid))
xtrain_ctv = ctv.transform(xtrain)
xvalid_ctv = ctv.transform(xvalid)
clf = LogisticRegression(C=1.0)
clf.fit(xtrain_ctv,ytrain)
predictions = clf.predict_proba(xvalid_ctv)
print("logloss: %0.3f " %multiclass_logloss(yvalid, predictions))
0.527 의 손실로, 더 나아지진 않았다.
2.4 Naive-Bayes
다음으로는 에전에 유명했던 간단한 모델인 Naive-Bayes를 사용해보자. 우선 두 데이터 셋으로 naive-bayes를 적용하면 어떻게 되는지 보자.
clf = MultinomialNB()
clf.fit(xtrain_tfv, ytrain)
predictions = clf.predict_proba(xvalid_tfv)
print('logloss: %0.3f' % multiclass_logloss(yvalid, predictions))
위의 두 모델보보는 훨씬 더 좋아졌다. 하지만, 여전히 logistic regression이 훨씬 더 좋다. 이 모델을 count data로 대신 사용하였을때는 어떨까?
clf = MultinomialNB()
clf.fit(xtrain_ctv, ytrain)
predictions = clf.predict_proba(xvalid_ctv)
print("logloss: %0.3f" % multiclass_logloss(yvalid, predictions))
더 나아진 것 같진 않다. 오래된 모델이 더 작동하는 것 가다.
2.4 SVM
이번엔 SVM을 적용해 본다. SVM은 시간이 많이 걸리므로 SVM 적용 전에 특이값 분해(Singular Value Decomposition)를 사용하여 TF-IDF로부터 feature의 갯수를 줄여준다.
또, SVM을 적용하기 전에 데이터를 표준화해 한다는 점을 주의해아한다.
svd = decomposition.TruncatedSVD(n_components=120)
svd.fit(xtrain_tfv)
xtrain_svd = svd.transform(xtrain_tfv)
xvalid_svd = svd.transform(xvalid_tfv)
# from SVD로 부터 데이터를 얻어 스케일링하기. 변수명은 다시 사용하여 짓는다.
scl = preprocessing.StandardScaler()
scl.fit(xtrain_svd)
xtrain_svd_scl = scl.transform(xtrain_svd)
xvalid_svd_scl = scl.transform(xvalid_svd)
# 자 이제 SVM을 돌려보자.
clf = SVC(C=1.0, probability=True)
clf.fit(xtrain_svd_scl, ytrain)
predictions = clf.predict_proba(xvalid_svd_scl)
print("logloss: %0.3f" % multiclass_logloss(yvalid, predictions))
2.5 xgboost
그 다음은 캐글에서 인기 있는 xgboost를 적용해보자.
clf = xgb.XGBClassifier(max_depth=7, n_estimators=200, colsample_bytree=0.8, subsample=0.8,
nthread=10, learning_rate=0.1)
clf.fit(xtrain_tfv.tocsc(), ytrain)
predictions = clf.predict_proba(xvalid_tfv.tocsc())
print("logloss: %0.3f "% multiclass_logloss(yvalid, predictions))
xtrain_ctv.tocsc()
# 같은 알고리즘으로 몇번씩 돌려보자.
clf = xgb.XGBClassifier(max_depth=7, n_estimators=200, colsample_bytree=0.8,
subsample=0.8, nthread=10, learning_rate=0.1)
clf.fit(xtrain_ctv.tocsc(), ytrain)
predictions = clf.predict_proba(xvalid_ctv.tocsc())
print("logloss: %0.3f" % multiclass_logloss(yvalid, predictions))
# 같은 알고리즘으로 몇번씩 돌려보자.
clf = xgb.XGBClassifier(max_depth=7, n_estimators=200, colsample_bytree=0.8,
subsample=0.8, nthread=10, learning_rate=0.1)
clf.fit(xtrain_ctv.tocsc(), ytrain)
predictions = clf.predict_proba(xvalid_ctv.tocsc())
print("logloss: %0.3f" % multiclass_logloss(yvalid, predictions))
# 이번엔 nthread 옵션만 넣어보자.
clf = xgb.XGBClassifier(nthread=10)
clf.fit(xtrain_svd, ytrain)
predictions=clf.predict_proba(xvalid_svd)
print("logloss : %0.3f" % multiclass_logloss(yvalid, predictions))
결과를 보면 xgboost는 운이 없는거 같다. 하지만 정확한 결과는 아니다. 왜냐하면 하이퍼 파라미터 최적화를 진행하지 않았기 때문이다. 지금부터 다뤄보자.
2.6 Grid Search
그리드 search는 하이퍼 파리미터 최적화 기법이다. 그리 효과적이진 않지만 사용하려는 그리드를 알고 있으면 좋은 결과를 얻을 수 있다. 이 포스트를 참고하여 일반적으로 사용해야하는 매개 변수를 지정하여 사용한다.
주로 사용하는 파라미터는 기억하면 좋다. 최적화를 위한 많은 하이퍼 파라미터는 효과적일 수도 있고 아닐 수도 있다. 이번 섹션에서는 logistic regression을 사용하여 grid search에 대하여 말해볼 것이다. grid search를 시작하기전에, scoring 함수를 만드는 것이 필요하다. scikit-learn의 함수인 make_scorer 를 사용하여 만들어보자.
mll_scorer = metrics.make_scorer(multiclass_logloss, greater_is_better=False, needs_proba=True)
다음으로는 pipeline이 필요하다. 여기 예제에서는 SVD,scaling, logistic regression으로 구성된 파이프라인을 사용할 것이다. 파이프라인에 있는 모듈을 하나만 사용하는 것보다 더 많이 사용하는 것이 좋다.
# SVD 초기화
svd = TruncatedSVD()
# standard scaler 초기화
scl = preprocessing.StandardScaler()
# 여기선 logistic regression을 사용할 것이다.
lr_model = LogisticRegression()
# 파이프라인 만들기
clf = pipeline.Pipeline([('svd', svd),
('scl',scl),
('lr', lr_model)])
이제 grid 파라미터가 필요하다.
param_grid = {'svd__n_components' : [120,180],
'lr__C' : [0.1, 1.0, 10],
'lr__penalty': ['l1', 'l2']}
SVD의 경우 120, 180 성분을 평가하고 로지스틱 회귀 분석의 경우 L1 및 L2 패널티를 사용하여 C의 세 가지 값을 평가한다. 이제 이러한 매개 변수에 대한 그리드 검색을 시작할 수 있다.
# Initialize Grid Search Model
model = GridSearchCV(estimator=clf, param_grid=param_grid, scoring=mll_scorer,
verbose=10, n_jobs=-1, iid=True, refit=True, cv=2)
# Fit Grid Search Model
model.fit(xtrain_tfv, ytrain) # we can use the full data here but im only using xtrain
print("Best score: %0.3f" % model.best_score_)
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
print("\t%s: %r" % (param_name, best_parameters[param_name]))
svm과 비슷한 점수를 받았다. 이 기술은 아래와 같이 xgboost 또한 multinomial naive bayesas 을 미세 조정하는데도 사용할 수 있다. 여기서는 tf-idf에서 사용한다.
nb_model = MultinomialNB()
# Create the pipeline
clf = pipeline.Pipeline([('nb', nb_model)])
# parameter grid
param_grid = {'nb__alpha': [0.001, 0.01, 0.1, 1, 10, 100]}
# Initialize Grid Search Model
model = GridSearchCV(estimator=clf, param_grid=param_grid, scoring=mll_scorer,
verbose=10, n_jobs=-1, iid=True, refit=True, cv=2)
# Fit Grid Search Model
model.fit(xtrain_tfv, ytrain) # we can use the full data here but im only using xtrain.
print("Best score: %0.3f" % model.best_score_)
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):
print("\t%s: %r" % (param_name, best_parameters[param_name]))
아마 결과는 순수 naive bayes 점수보다 8% 개선 됬을 것이다. NLP 문제에서는 단어 벡터를 보는 것이 일반적이다. 단어 벡터는 데이터에 대한 많은 통찰력을 제공한다. 자세히 실습을 진행하고 싶으면 kaggle에서 참고해보자. 여기선 바로 딥러닝으로 들어가보겠다.
2.7 WordVector
# load the GloVe vectors in a dictionary:
embeddings_index = {}
f = open('../data/glove.840B.300d.txt', encoding='utf8')
for line in tqdm(f):
values = line.split()
word = ''.join(values[:-300])
coefs = np.asarray(values[-300:], dtype='float32')
embeddings_index[word] = coefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))
2.8 Deep Learning
지금은 딥러닝의 시대이다. 우리는 신경망 네트워크를 학습하지않고는 살아갈 수 없다. 여기에 LSTM과 간단한 dense network를 활용하여 GloVe 변수를 학습한다. 자 dense network를 첫번째로 시작해봅시다.
import nltk
nltk.download('punkt')
# this function creates a normalized vector for the whole sentence
def sent2vec(s):
# words = str(s).lower().decode('utf-8')
words = str(s).lower()
words = word_tokenize(words)
words = [w for w in words if not w in stop_words]
words = [w for w in words if w.isalpha()]
M = []
for w in words:
try:
M.append(embeddings_index[w])
except:
continue
M = np.array(M)
v = M.sum(axis=0)
if type(v) != np.ndarray:
return np.zeros(300)
return v / np.sqrt((v ** 2).sum())
xtrain_glove = [sent2vec(x) for x in tqdm(xtrain)]
xvalid_glove = [sent2vec(x) for x in tqdm(xvalid)]
scl = preprocessing.StandardScaler()
xtrain_glove_scl = scl.fit_transform(xtrain_glove)
xvalid_glove_scl = scl.transform(xvalid_glove)
ytrain_enc = np_utils.to_categorical(ytrain)
yvalid_enc = np_utils.to_categorical(yvalid)
model = Sequential()
model.add(Dense(300, input_dim=300, activation='relu'))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(Dense(300, activation='relu'))
model.add(Dropout(0.3))
model.add(BatchNormalization())
model.add(Dense(3))
model.add(Activation('softmax'))
# compile the model
model.compile(loss='categorical_crossentropy', optimizer='adam')
model.fit(xtrain_glove_scl, y= ytrain_enc, batch_size=64, epochs=5, verbose=1,
validation_data=(xvalid_glove_scl, yvalid_enc))
더 나은 결과를 얻기 위해서는 신경 네트워크의 매개 변수를 계속해서 조정하고, 더 많은 레이어를 추가하고, dropout을 늘려야 한다. 하지만 신경네트워크는 최적화 없이도 구현 및 실행이 빠르며 xgboost보다 더 나은 결과를 얻을 수 있다는 것을 확인할 수 있다.
더 나아가려면 LSTM을 사용하여 텍스트 데이터를 토큰화해야 한다.
# 케라스 사용하여 토큰화하기
token = text.Tokenizer(num_words=None)
max_len = 70
token.fit_on_texts(list(xtrain) + list(xvalid))
xtrain_seq = token.texts_to_sequences(xtrain)
xvalid_seq = token.texts_to_sequences(xvalid)
# zero pad the sequences
xtrain_pad = sequence.pad_sequences(xtrain_seq, maxlen=max_len)
xvalid_pad = sequence.pad_sequences(xvalid_seq, maxlen=max_len)
word_index = token.word_index
# create an embedding matrix for the words we have in the dataset
embedding_matrix = np.zeros((len(word_index) + 1, 300))
for word, i in tqdm(word_index.items()):
embedding_vector = embeddings_index.get(word)
if embedding_vector is not None:
embedding_matrix[i] = embedding_vector
# A simple LSTM with glove embeddings and two dense layers
model = Sequential()
model.add(Embedding(len(word_index) + 1,
300,
weights=[embedding_matrix],
input_length=max_len,
trainable=False))
model.add(SpatialDropout1D(0.3))
model.add(LSTM(100, dropout=0.3, recurrent_dropout=0.3))
model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))
model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))
model.add(Dense(3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
model.fit(xtrain_pad, y=ytrain_enc, batch_size=512, epochs=100,
verbose=1, validation_data=(xvalid_pad, yvalid_enc))
# A simple LSTM with glove embeddings and two dense layers
model = Sequential()
model.add(Embedding(len(word_index) + 1,
300,
weights=[embedding_matrix],
input_length=max_len,
trainable=False))
model.add(SpatialDropout1D(0.3))
model.add(LSTM(300, dropout=0.3, recurrent_dropout=0.3))
model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))
model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))
model.add(Dense(3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
# Fit the model with early stopping callback
earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=3, verbose=0, mode='auto')
model.fit(xtrain_pad, y=ytrain_enc, batch_size=512, epochs=100,
verbose=1, validation_data=(xvalid_pad, yvalid_enc), callbacks=[earlystop])
이제 매우 가까워져 간다. GRU 두개 레이어를 더 해보자
# GRU with glove embeddings and two dense layers
model = Sequential()
model.add(Embedding(len(word_index) + 1, 300
weights=[embedding_matrix],
input_length=max_len,
trainable=False))
model.add(SpatialDropout1D(0.3))
model.add(GRU(300, dropout=0.3, recurrent_dropout=0.3, return_sequences=True))
model.add(GRU(300, dropout=0.3, recurrent_dropout=0.3))
model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))
model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))
model.add(Dense(3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=3, verbose=0,
mode='auto')
model.fit(xtrain_pad, y =ytrain_enc, batch_size=512,epochs=100,
verbose=1,validation_data=(xvalid_pad, yvalid_enc), callback=[earlystop])
'Competition > Kaggle' 카테고리의 다른 글
[kaggle][필사] Credit Card Fraud Detection (1) (0) | 2021.02.01 |
---|---|
[kaggle][필사] 2018 Data Science Bowl (0) | 2020.10.28 |
[kaggle][필사] Zillow Prize: Zillow’s Home Value Prediction (2) (0) | 2020.10.19 |