Stacking ile Kelimelerden Dil Tahmini
Published:
English Version
Devamı: Streamlit ve Heroku ile Canlıya Çıkmak
Problem
- Türkçe ve İngilizce kelimelerden oluşan bir veri setimiz var ve kelimelere göre dili algılamak istiyoruz. Tahmin etmek istediğimiz hedef değişken ise
ìs_turkish
adında ikili (binary) bir değişken.
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics import f1_score, accuracy_score, confusion_matrix, precision_score, recall_score
from sklearn.linear_model import RidgeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import KFold, StratifiedKFold, train_test_split
from lightgbm import LGBMClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
import lightgbm as lgb
from sklearn.svm import LinearSVC
import gc
Veri Önişleme
- Kelimelere ait meta değişkenler olan harf sayısı, sesli harf sayısı ve sessiz harf sayısı hesaplanmıştır.
- TF-IDF ve Count Vectorizer kullanılarak kelimeler karakter bazında vektör haline getirilmiştir.
- Vektörler oluşturulduktan sonra train ve test setleri 0.8 ve 0.2 oranlarında ayrılmıştır (Pareto prensibi).
- Train setinde negatif olan gözlemler için downsampling yapılmıştır.
seed = 1001
df = pd.read_csv("language_detection.csv")
df.head()
Words | is_turkish | |
---|---|---|
0 | ihtiva | 1 |
1 | ajenjo | 0 |
2 | lamby | 0 |
3 | epee | 0 |
4 | ilmi | 1 |
df["is_turkish"].value_counts()
0 17332
1 2123
Name: is_turkish, dtype: int64
df["is_turkish"].value_counts(normalize = True)
0 0.890876
1 0.109124
Name: is_turkish, dtype: float64
Toplamda 19455 kelime var ve bunların %10 Türkçe. Hedef değişkenin dağılımı itibariyle de problemin dengesiz (imbalanced/unbalanced) olduğu söylenebilir!
df["Words"].apply(lambda x: len(x)).value_counts()
6 10550
5 6108
4 2797
Name: Words, dtype: int64
Kelimeler ise en az 4 en fazla 6 harfe sahip. Bu bilgiyi kullanacağız ileride.
Metin verilerinden çıkarılabilecek en basit düzeydeki değişkenler meta-features olarak isimlendirilmektedir. Buna göre aşağıdaki değişkenkler hesaplanmıştır;
- Sesli harf sayısı
- Sessiz harf sayısı
- Toplam harf sayısı
Bunların yanı sıra tekil (unique) sesli, tekil sessiz harf sayısı hesaplanabilir ya da elimizdekinden farklı olarak cümleler içeren bir veri seti için noktalama işaretlerinin sayısı, boşluk karakterlerinin sayısı, tekil kelime sayısı gibi daha pek çok farklı değişken çıkarılabilir.
df['Vowels'] = df.Words.str.lower().str.count(r'[aeiou]')
df['Consonant'] = df.Words.str.lower().str.count(r'[a-z]') - df['Vowels']
df["Len"] = df.Words.apply(lambda x: len(x))
df.head()
Words | is_turkish | Vowels | Consonant | Len | |
---|---|---|---|---|---|
0 | ihtiva | 1 | 3 | 3 | 6 |
1 | ajenjo | 0 | 3 | 3 | 6 |
2 | lamby | 0 | 1 | 4 | 5 |
3 | epee | 0 | 3 | 1 | 4 |
4 | ilmi | 1 | 2 | 2 | 4 |
Metinlerin işleyebilmek için öncelikle sayısal değerlere çevirmeliyiz. Bunun için ise Count Vectorizer ve TF-IDF yöntemlerinden yararlanacağız. Yöntemlerin detayları için;
- Count Vectorizer
- TF-IDF
n-gram oluştururken bunu karakter özelinde mi yoksa kelime özelinde mi yapacağımızı analyze argümanı ile belirtiyoruz. Burada kelime ve karakter olarak denedim fakat en iyi sonucu karakter ile aldım. Bunun nedeninin de elimizde tek kelime olması (muhtemelen). Zira elimizde cümleler olsaydı kelimeler üzerinden ilerleyebilecektik.
N-Gram’ları oluştururken de 1,6’lık olacak şekilde oluşturdum. 6 sayısını ise elimizdeki kelimelerin en fazla 6 harfli olması nedeniyle seçtim. Farklı kombinasyonlar ile (n,6 gibi) farklı skorlar elde etmek mümkün.
Bu aşamada göz önünde bulundurmanız gerken şeylerden biri de sahip olduğunuz RAM. Muhtemelen vektör boyutlarını (max_features) arttırdıkça çok daha iyi sonuçlar alacaksınız fakat hem vektörlerin dönüşümü sırasında hem de modelleme aşamasında OOM (out of memory) hatasıyla karşılaşabilirsiniz. Bu nedenle biraz deneme yanılma ile maksimum RAM limitini bilerek ve bunun altında kalmaya çalışmak çok daha sağlıklı.
word = False
word_tfidf = False
count_para_word = {
"analyzer": "word",
"dtype": np.float32,
}
count_para_char = {
"analyzer": "char_wb",
"dtype": np.float32, # RAM için bir diğer optimize edilebilecek argüman.
"ngram_range": (1, 6) # maksimum harf sayımız 6 olduğu için üst sınır 6
}
if word:
vectorizer = CountVectorizer(
max_features=50000,
**count_para_word)
else:
vectorizer = CountVectorizer(
max_features=50000,
**count_para_char)
tfidf_para_word = {
"analyzer": "word",
"sublinear_tf": True,
"dtype": np.float32,
"norm": 'l2',
"min_df": 5,
"max_df": 0.9,
"smooth_idf": False
}
tfidf_para_char = {
"analyzer": "char_wb",
"dtype": np.float32,
"ngram_range": (1, 6) # maksimum harf sayımız 6 olduğu için üst sınır 6
}
if word_tfidf:
vectorizer_tfidf = TfidfVectorizer(
max_features=5000,
**tfidf_para_word)
else:
vectorizer_tfidf = TfidfVectorizer(
max_features=50000,
**tfidf_para_char)
vectorizer.fit(df["Words"])
vectorizer_tfidf.fit(df["Words"])
Metni sayıya dönüştürmeden önce veriyi train ve test olacak şekilde bölüyoruz. Burada problemimiz dengesiz olduğu için bu dağılımı korumak adına stratify argümanı ile veriyi y değişkeni üzerinden tabakalandırarak bölüyoruz ve Pareto prensibi ile %80’e %20 olacak şekilde ayırıyoruz.
train_vector, test_vector = train_test_split(df, test_size = 0.2, stratify = df["is_turkish"], random_state = seed)
Ayırdığımız train setinde ise downsampling yaparak örneklemi küçültüyoruz. Pozitif olan yani Türkçe olan kelimelere karşılık İngilizce kelimelerin %40’ını (rastgele) alarak yeni bir alt veri seti oluşturuyoruz. (random_state
kullanarak tekrar edilebilir bir işlem olmasını sağlıyoruz).
train_vector = train_vector[(train_vector.is_turkish == 1) | (
train_vector.is_turkish == 0).sample(frac=0.4, random_state=seed)].sample(
frac=1, random_state=seed).reset_index(drop=True)
y_train = train_vector["is_turkish"].values
y_test = test_vector["is_turkish"].values
TF-IDF ve Count Vec. dönüşümlerini train ve test verisi için yapıyoruz.
train_vector_ = vectorizer.transform(train_vector["Words"])
test_vector_ = vectorizer.transform(test_vector["Words"])
train_vector_tfidf = vectorizer_tfidf.transform(train_vector["Words"])
test_vector_tfidf = vectorizer_tfidf.transform(test_vector["Words"])
from scipy.sparse import hstack, csr_matrix
Count Vec. ve TF-IDF’i kelimeler üzerinde uyguladık ve artık elimizde seyrek (sparse) matrisler var. Aynı zamanda daha önce oluşturduğumuz meta değişkenler vardı. Şimdi onları birleştirerek modele girecek olan veri setinin nihai halini oluşturuyoruz. Bunun için de CSR (compressed sparse row matrix) formatını kullanıyoruz.
train_vector = hstack([
train_vector_,
train_vector_tfidf,
csr_matrix(train_vector.Vowels.values.reshape(-1, 1)),
csr_matrix(train_vector.Consonant.values.reshape(-1, 1)),
csr_matrix(train_vector.Len.values.reshape(-1, 1))
])
test_vector = hstack([
test_vector_,
test_vector_tfidf,
csr_matrix(test_vector.Vowels.values.reshape(-1, 1)),
csr_matrix(test_vector.Consonant.values.reshape(-1, 1)),
csr_matrix(test_vector.Len.values.reshape(-1, 1))
])
train_vector = train_vector.tocsr()
test_vector = test_vector.tocsr()
train_vector.shape, y_train.shape
((7254, 100003), (7254,))
test_vector.shape, y_test.shape
((3891, 100003), (3891,))
del train_vector_, test_vector_
del train_vector_tfidf, test_vector_tfidf
(RAM’den tasarruf!)
Modelleme
Kaggle’da katıldığım ilk yarışma olan Allstate Claims Severity yarışmasında Stacking Starter olarak Faron tarafından paylaşılan Stacking kodundan yararlanacağız.
Buradaki asıl soru ise şu; Stacking nedir? Basitçe anlatmak gerekirse normal şartlarda modellerimizi bağımsız değişkenleri (independent variables a.k.a x’ler) kullanarak bağımlı değişkeni (ya da değişkenleri - dependent variable) tahmin edecek şekilde kuruyoruz. Fakat Stacking ile bu işi bir adım daha ileriye götürerek öğrendiklerimizden öğrenmeye çalışıyoruz. Artık bir modelin çıktısı (tahminleri) başka bir modelde girdi halini alıyor.
Peki nelere dikkat etmek gerekiyor? Öncelikle single model dediğimiz yani çıktılarını kullanacağımız tekli modellerde uyguladığımız CV şemasını uygulamamız gerekiyor. Herhangi bir leakage olmasını istemeyoruz. Bir de iyi sonuç alabilmek için model çeşitliliğini (diversity) arttırmamız gerekiyor. Bu nedenle bu notebook’ta Random Forest ve Lojistik Regresyon için iki farklı parametre seti ile modeller kurdum. Genel olarak Stacking aşamasında skorunuzu arttırmak istiyorsanız çare bol bol farklı model ve farklı subset’ler (farklı değişkenlere sahip alt veri setleri) ile eğtilimiş modeller. Bu noktada modellere ait tahminler arasındaki korelasyona bakmakta fayda var. Ayrıca mümkün olduğunca yüksek skora sahip düşük korelasyonlu modelleri kullanmak gerekir. Yoksa benzer skora sahip, aralarında 0.99998 korelasyon olan modellerin bir getirisi olmayacaktır!
Stacking’i özetlemek gerekirse de aşağıdaki görsel bize bir hayli yardımcı olacaktır.
Stacking için kaynaklar
- Introduction to Ensembling/Stacking in Python
- Kaggle Ensembling Guide
- Stacked generalization, Wolpert DH.
- Stacked Regressions : Top 4% on LeaderBoard
Aşağıdaki kod bloğu bildiğim kadarıyla Faron’a ait bu nedenle referans olarak linkimizi ekleyelim.
And the oscar goes to Faron: https://www.kaggle.com/mmueller/stacking-starter
Buna ek olarak ben predict_proba
metodunu ve fold skorlarını görmek adına ufak print
kısımlarını ekledim.
class SklearnWrapper(object):
def __init__(self, clf, seed=0, params=None, seed_bool = True):
if(seed_bool == True):
params['random_state'] = seed
self.clf = clf(**params)
def train(self, x_train, y_train):
self.clf.fit(x_train, y_train)
def predict(self, x):
return self.clf.predict(x)
def predict_proba(self, x):
return self.clf.predict_proba(x)
def get_oof(clf, x_train, y, x_test, prob = False):
oof_train = np.zeros((ntrain,))
oof_test = np.zeros((ntest,))
oof_test_skf = np.empty((NFOLDS, ntest))
for i, (train_index, test_index) in enumerate(kf.split(x_train, y)):
x_tr = x_train[train_index]
y_tr = y[train_index]
x_te = x_train[test_index]
y_te = y[test_index]
clf.train(x_tr, y_tr)
if prob:
oof_train[test_index] = clf.predict_proba(x_te)[:,1]
oof_test_skf[i, :] = clf.predict_proba(x_test)[:,1]
else:
oof_train[test_index] = clf.predict(x_te)
oof_test_skf[i, :] = clf.predict(x_test)
print("Fold:", i+1)
print("F1", np.round(f1_score(y_te, np.round(oof_train[test_index])),6))
print("\n")
oof_test[:] = oof_test_skf.mean(axis=0)
return oof_train.reshape(-1, 1), oof_test.reshape(-1, 1)
def scorer(y_true, y_pred, is_return = False):
if is_return:
return [f1_score(y_true, np.round(y_pred)), accuracy_score(y_true, np.round(y_pred)), recall_score(y_true, np.round(y_pred)), precision_score(y_true, np.round(y_pred))]
else:
print("F1: {:.4f}".format(f1_score(y_true, np.round(y_pred))))
print("Accuracy: {:.4f}".format(accuracy_score(y_true, np.round(y_pred))))
print("Recall: {:.4f}".format(recall_score(y_true, np.round(y_pred))))
print("Precision: {:.4f}".format(precision_score(y_true, np.round(y_pred))))
print("AUC: {:.4f}".format(roc_auc_score(y_true, y_pred)))
print((confusion_matrix(y_true, np.round(y_pred))))
NFOLDS = 5
ntrain = train_vector.shape[0]
ntest = test_vector.shape[0]
kf = StratifiedKFold(n_splits=NFOLDS, shuffle=True, random_state=seed)
Kullanılan Algoritmalar
- Ridge Regresyon
- Naive Bayes
- SVM (Lineer Kernel ile)
- Lojistik Regresyon
- Random Forest
- GBM (LGB)
Modellere ait parametreler ise elle (deneme yanılma) ayarlandı. Pek tabii farklı yöntemler (grid & bayesian) ile daha iyi sonuçlar elde etmek mümkün.
Ridge
ridge_params = {'alpha':250.0, 'fit_intercept':True, 'normalize':True, 'copy_X':True,
'max_iter':None, 'tol':0.001, 'solver':'auto', 'random_state':seed,
"class_weight": "balanced"
}
ridge = SklearnWrapper(clf=RidgeClassifier, seed = seed, params = ridge_params)
%%time
ridge_oof_train, ridge_oof_test = get_oof(ridge, train_vector, y_train, test_vector, prob=False)
Fold: 1
F1 0.642005
Fold: 2
F1 0.596871
Fold: 3
F1 0.633735
Fold: 4
F1 0.608696
Fold: 5
F1 0.612717
Wall time: 190 ms
scorer(ridge_oof_train, y_train)
F1: 0.6186
Accuracy: 0.7760
Recall: 0.5142
Precision: 0.7762
[[4311 380]
[1245 1318]]
scorer(ridge_oof_test, y_test)
F1: 0.4139
Accuracy: 0.7708
Recall: 0.2871
Precision: 0.7412
[[2684 110]
[ 782 315]]
SVM
svm_params = {"C": 3, "max_iter": 10000, "class_weight": "balanced"}
svm = SklearnWrapper(clf=LinearSVC, seed = seed, params = svm_params)
%%time
svm_oof_train, svm_oof_test = get_oof(svm, train_vector.toarray(), y_train, test_vector.toarray(), prob=False)
Fold: 1
F1 0.654321
Fold: 2
F1 0.630265
Fold: 3
F1 0.622291
Fold: 4
F1 0.636364
Fold: 5
F1 0.638427
Wall time: 44 s
scorer(svm_oof_train, y_train)
F1: 0.6364
Accuracy: 0.8357
Recall: 0.6601
Precision: 0.6143
[[5019 655]
[ 537 1043]]
scorer(svm_oof_test, y_test)
F1: 0.5319
Accuracy: 0.8810
Recall: 0.4663
Precision: 0.6188
[[3165 162]
[ 301 263]]
LightGBM
lgbm_params = {
'task': 'train',
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'binary_error',
'num_leaves': 17,
"max_depth": -1,
'feature_fraction': 0.95,
'bagging_fraction': 0.9,
'bagging_freq': 8,
'learning_rate': 0.075,
'verbose': 0,
"n_estimator": 50,
"pos_bagging_fraction": 0.8,
"neg_bagging_fraction": 0.6,
}
%%time
lgbc = SklearnWrapper(clf=LGBMClassifier, seed = seed, params = lgbm_params)
lgbc_oof_train, lgbc_oof_test = get_oof(lgbc, train_vector, y_train, test_vector, prob=True)
Fold: 1
F1 0.680062
Fold: 2
F1 0.594324
Fold: 3
F1 0.664516
Fold: 4
F1 0.65
Fold: 5
F1 0.664596
Wall time: 3.81 s
scorer(lgbc_oof_train, y_train)
F1: 0.6514
Accuracy: 0.8467
Recall: 0.6964
Precision: 0.6119
[[5103 659]
[ 453 1039]]
scorer(lgbc_oof_test, y_test)
F1: 0.5293
Accuracy: 0.8885
Recall: 0.4909
Precision: 0.5741
[[3213 181]
[ 253 244]]
Random Forest
rf_params = {"n_jobs":12,
"n_estimators": 380,
"class_weight": "balanced",
"min_samples_split": 5}
%%time
rf = SklearnWrapper(clf=RandomForestClassifier, seed = seed, params = rf_params)
rf_oof_train, rf_oof_test = get_oof(rf, train_vector, y_train, test_vector, prob=True)
Fold: 1
F1 0.635417
Fold: 2
F1 0.561798
Fold: 3
F1 0.620939
Fold: 4
F1 0.616667
Fold: 5
F1 0.623917
Wall time: 32.2 s
scorer(rf_oof_train, y_train)
F1: 0.6125
Accuracy: 0.8482
Recall: 0.7612
Precision: 0.5124
[[5283 828]
[ 273 870]]
scorer(rf_oof_test, y_test)
F1: 0.5308
Accuracy: 0.9041
Recall: 0.5703
Precision: 0.4965
[[3307 214]
[ 159 211]]
rf_2_params = {"n_jobs":12,
"max_depth": 15,
"n_estimators": 500,
"criterion": "gini",
"min_samples_split": 5,
"min_samples_leaf": 3,
"class_weight": "balanced"}
%%time
rf_2 = SklearnWrapper(clf=RandomForestClassifier, seed = seed, params = rf_2_params)
rf_2_oof_train, rf_2_oof_test = get_oof(rf_2, train_vector, y_train, test_vector, prob=True)
Fold: 1
F1 0.675393
Fold: 2
F1 0.625337
Fold: 3
F1 0.655827
Fold: 4
F1 0.65404
Fold: 5
F1 0.657068
Wall time: 10.2 s
scorer(rf_2_oof_train, y_train)
F1: 0.6537
Accuracy: 0.8186
Recall: 0.5909
Precision: 0.7314
[[4696 456]
[ 860 1242]]
scorer(rf_2_oof_test, y_test)
F1: 0.4760
Accuracy: 0.8314
Recall: 0.3603
Precision: 0.7012
[[2937 127]
[ 529 298]]
Lojistik Regresyon
lr_params = {"n_jobs":12,
"solver": "saga",
"C": 10,
"max_iter": 1000}
%%time
lr = SklearnWrapper(clf=LogisticRegression, seed = seed, params = lr_params)
lr_oof_train, lr_oof_test = get_oof(lr, train_vector, y_train, test_vector, prob = True)
Fold: 1
F1 0.6752
Fold: 2
F1 0.628205
Fold: 3
F1 0.644013
Fold: 4
F1 0.645161
Fold: 5
F1 0.662441
Wall time: 16.9 s
scorer(lr_oof_train, y_train)
F1: 0.6510
Accuracy: 0.8485
Recall: 0.7064
Precision: 0.6037
[[5130 673]
[ 426 1025]]
scorer(lr_oof_test, y_test)
F1: 0.5527
Accuracy: 0.8964
Recall: 0.5231
Precision: 0.5859
[[3239 176]
[ 227 249]]
lr_2_params = {
"solver": "liblinear",
"class_weight": "balanced",
"max_iter": 750,
"C":25,
"penalty": "l1"}
%%time
lr_2 = SklearnWrapper(clf=LogisticRegression, seed = seed, params = lr_2_params)
lr_2_oof_train, lr_2_oof_test = get_oof(lr_2, train_vector, y_train, test_vector, prob = True)
Fold: 1
F1 0.666667
Fold: 2
F1 0.642314
Fold: 3
F1 0.660633
Fold: 4
F1 0.623748
Fold: 5
F1 0.670537
Wall time: 2.95 s
scorer(lr_2_oof_train, y_train)
F1: 0.6527
Accuracy: 0.8380
Recall: 0.6552
Precision: 0.6502
[[4975 594]
[ 581 1104]]
scorer(lr_2_oof_test, y_test)
F1: 0.5309
Accuracy: 0.8792
Recall: 0.4610
Precision: 0.6259
[[3155 159]
[ 311 266]]
Threshold Optimization (Eşik Optimizasyonu)
Bu noktada modellerden elde ettiğimiz olasılıkları kullandığımız metrik olan F1 için maksimize edeceğiz. Bu nedenle F1 skorunun en büyüklendiği kesim noktasını bulmamız gerekiyor. Benim bunu yapma sebebim ise Ridge ve SVM’in olasılık döndürmemesi. Tüm girdilerin aynı formatta olması adına böyle bir yuvarlama yapıyorum fakat olasılık olarak girdi vermeniz durumunda da model olasılık tahmin edeceği için zorunlu bir durum değil.
SVM ve Ridge’e ait eşiklerin 0 gelme nedeni ise bahsettiğim gibi olasılık yerine halihazırda yuvarlanmış değer dönüyor olması.
def threshold_search(y_true, y_proba):
best_threshold = 0
best_score = 0
for threshold in [i * 0.01 for i in range(100)]:
score = f1_score(y_true=y_true, y_pred=y_proba > threshold)
if score > best_score:
best_threshold = threshold
best_score = score
search_result = {'threshold': best_threshold, 'f1': best_score}
return search_result
ridge_thr = threshold_search(y_train, ridge_oof_train)
ridge_thr
{'threshold': 0.0, 'f1': 0.6186341234452007}
lgb_thr = threshold_search(y_train, lgbc_oof_train)
lgb_thr
{'threshold': 0.39, 'f1': 0.6808743169398906}
rf_thr = threshold_search(y_train, rf_oof_train)
rf_thr
{'threshold': 0.36, 'f1': 0.6817420435510888}
rf2_thr = threshold_search(y_train, rf_2_oof_train)
rf2_thr
{'threshold': 0.5, 'f1': 0.6536842105263159}
lr_thr = threshold_search(y_train, lr_oof_train)
lr_thr
{'threshold': 0.19, 'f1': 0.6791243993593166}
lr2_thr = threshold_search(y_train, lr_2_oof_train)
lr2_thr
{'threshold': 0.37, 'f1': 0.6624857468643103}
svm_thr = threshold_search(y_train, svm_oof_train)
svm_thr
{'threshold': 0.0, 'f1': 0.6363636363636365}
Stacking
Stacking için yeni modellerden çıkan tahminlerimizi kullanarak girdi matrisini oluşturuyoruz.
train_df = np.concatenate((
ridge_oof_train,
(lr_oof_train > lr_thr["threshold"]).astype(int),
(lr_2_oof_train > lr2_thr["threshold"]).astype(int),
(rf_oof_train > rf_thr["threshold"]).astype(int),
(rf_2_oof_train > rf2_thr["threshold"]).astype(int),
(lgbc_oof_train > lgb_thr["threshold"]).astype(int),
svm_oof_train
),
axis=1)
test_df = np.concatenate((
ridge_oof_test,
(lr_oof_test > lr_thr["threshold"]).astype(int),
(lr_2_oof_test > lr2_thr["threshold"]).astype(int),
(rf_oof_test > rf_thr["threshold"]).astype(int),
(rf_2_oof_test > rf2_thr["threshold"]).astype(int),
(lgbc_oof_test > lgb_thr["threshold"]).astype(int),
svm_oof_test
),
axis=1)
# Yeni veri setimiz
pd.DataFrame(train_df).head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|
0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 |
4 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
y_train.shape, train_df.shape
((7254,), (7254, 7))
Stacking modeli olarak LightGBM kullanıyoruz. Burada dikkat edilmesi gerekenlerden biri stacking modelinin bir önceki adımdaki modellere oranla daha basit bir model olarak seçilmesi. LightGBM için konuşacak olursak maksimum derinlik parametresi tekli modeller için örneğin 7 ise bu aşamada kuracağımız modelin derinliğinin 7’den küçük olması gerekiyor. Bu şekilde modelin karmaşıklığını (model complexity) azaltarak varyans - yanlılık (variance - bias trade-off) değiş tokuşunu kontrol altında tutup aşırı öğrenmenin (overfit) önüne geçebiliriz.
Bu aşamada dilersek yine bir önceki aşamadaki gibi aynı CV şemasını kullanarak bir çıktı elde edebiliriz. Fakat burada LightGBM’in built-in CV fonksiyonunu kullanarak ağaç sayısını belirliyoruz. Elde edilen ağaç sayısını da %10 arttırarak tekli modeldeki ağaç sayısı seçiyoruz.
dtrain = lgb.Dataset(train_df, label=y_train)
def lgb_f1_score(y_hat, data):
y_true = data.get_label()
y_hat = np.round(y_hat)
return 'f1', f1_score(y_true, y_hat), True
lgbm_params = {
'task': 'train',
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'auc',
'num_leaves': 11,
"max_depth": 4,
'feature_fraction': 0.9,
'bagging_fraction': 0.95,
'bagging_freq': 12,
'learning_rate': 0.05,
"scale_pos_weight": 0.9,
'verbose': 0,
"pos_bagging_fraction": 0.8,
"neg_bagging_fraction": 0.6,
}
cv = lgb.cv(lgbm_params,
dtrain,
num_boost_round=1000,
folds=kf,
verbose_eval=5,
early_stopping_rounds=20,
feval=lgb_f1_score,
eval_train_metric=False)
[5] cv_agg's auc: 0.874307 + 0.011259 cv_agg's f1: 0 + 0
[10] cv_agg's auc: 0.876046 + 0.0124176 cv_agg's f1: 0 + 0
[15] cv_agg's auc: 0.876212 + 0.0122827 cv_agg's f1: 0.631688 + 0.0228328
[20] cv_agg's auc: 0.87627 + 0.0122567 cv_agg's f1: 0.645804 + 0.0201663
[25] cv_agg's auc: 0.876185 + 0.0122421 cv_agg's f1: 0.668783 + 0.0182651
[30] cv_agg's auc: 0.876121 + 0.0123478 cv_agg's f1: 0.676299 + 0.0233192
[35] cv_agg's auc: 0.876392 + 0.0123643 cv_agg's f1: 0.683745 + 0.0182382
[40] cv_agg's auc: 0.876375 + 0.0123732 cv_agg's f1: 0.686564 + 0.0171678
[45] cv_agg's auc: 0.876349 + 0.0124942 cv_agg's f1: 0.68731 + 0.0191348
[50] cv_agg's auc: 0.876425 + 0.0125615 cv_agg's f1: 0.688089 + 0.0185613
[55] cv_agg's auc: 0.876408 + 0.0123638 cv_agg's f1: 0.686265 + 0.0162085
[60] cv_agg's auc: 0.876458 + 0.0123725 cv_agg's f1: 0.687302 + 0.0159655
[65] cv_agg's auc: 0.876493 + 0.0123624 cv_agg's f1: 0.687565 + 0.0165241
[70] cv_agg's auc: 0.876494 + 0.0122952 cv_agg's f1: 0.690089 + 0.0192894
[75] cv_agg's auc: 0.876437 + 0.0123238 cv_agg's f1: 0.690022 + 0.0194542
[80] cv_agg's auc: 0.876431 + 0.0123069 cv_agg's f1: 0.690445 + 0.0192664
[85] cv_agg's auc: 0.876308 + 0.012435 cv_agg's f1: 0.690434 + 0.0194785
cv["f1-mean"][-1]
0.6900975355028047
best_n = len(cv["f1-mean"])
best_n
68
model = lgb.train(lgbm_params, dtrain, num_boost_round=int(best_n * 1.1))
test_preds = model.predict(test_df)
scorer(y_test, np.round(test_preds))
F1: 0.5632
Accuracy: 0.8836
Recall: 0.6871
Precision: 0.4771
[[3146 320]
[ 133 292]]
results_df = pd.DataFrame([scorer(ridge_oof_test, y_test, is_return=True),
scorer(svm_oof_test, y_test, is_return=True),
scorer(lgbc_oof_test, y_test, is_return=True),
scorer(rf_oof_test, y_test, is_return=True),
scorer(rf_2_oof_test, y_test, is_return=True),
scorer(lr_oof_test, y_test, is_return=True),
scorer(lr_2_oof_test, y_test, is_return=True),
scorer(y_test, np.round(test_preds), is_return=True)], columns=["F1", "Accuracy", "Recall", "Precision"], index = ["Ridge", "SVM", "LGB", "RF", "RF2", "LR", "LR2", "Stacked Model"])
results_df
F1 | Accuracy | Recall | Precision | |
---|---|---|---|---|
Ridge | 0.413929 | 0.770753 | 0.287147 | 0.741176 |
SVM | 0.531850 | 0.881007 | 0.466312 | 0.618824 |
LGB | 0.529284 | 0.888461 | 0.490946 | 0.574118 |
RF | 0.530818 | 0.904138 | 0.570270 | 0.496471 |
RF2 | 0.476038 | 0.831406 | 0.360339 | 0.701176 |
LR | 0.552719 | 0.896428 | 0.523109 | 0.585882 |
LR2 | 0.530938 | 0.879208 | 0.461005 | 0.625882 |
Stacked Model | 0.563163 | 0.883577 | 0.687059 | 0.477124 |
Sonuç itibariyle en iyi F1 skorunu Stacking modeli ile elde ettik. Fakat burada Precision ve Recall arasında ufak bir değiş tokuş (trade-off) yapıyoruz. Bu nedenle problemin içeriği & kısıtları, dengesizlik oranı (imbalanced ratio) gibi durumlar göz önüne alınarak hem önişleme hem de modelleme aşamalarının tekrar gözden geçirilmesi gerekir. Örneğin downsampling oranının değiştirilmesi, parametre optimizasyonu ya da maliyet fonksiyonun (cost function) değiştirilmesi gibi değişiklikler yapılabilir.
Stacking yerine de seçilen modeller için lineer ağırlıklandırma kullanılarak blending denenebilir (weighted linear blending).
Son olarak bu notebook’ta Stacking yöntemlerine basit bir giriş yapmayı ve bunun yanı sıra metin işlemek için Deep Learning harici, back to basics diyebileceğimiz yöntemleri göstermeyi amaçladım.