Procedura oversampling dla Random Forest Classifier

Przy budowie modelów klasyfikacji 0-1 występuje problem  zbilansowanych zbiorów

In [1]:
import numpy as np
import pandas as pd
#import xgboost as xgb
import seaborn as sns

from sklearn.preprocessing import LabelEncoder
import matplotlib.pylab as plt

from pylab import plot, show, subplot, specgram, imshow, savefig
from sklearn import preprocessing
#from sklearn import cross_validation, metrics
from sklearn.preprocessing import Normalizer
#from sklearn.cross_validation import cross_val_score
from sklearn.preprocessing import Imputer

import matplotlib.pyplot as plote

plt.style.use('ggplot')
In [2]:
df = pd.read_csv('c:/1/bank.csv')
df.head()
Out[2]:
Unnamed: 0 Unnamed: 0.1 age job marital education default housing loan contact campaign pdays previous poutcome emp_var_rate cons_price_idx cons_conf_idx euribor3m nr_employed y
0 0 0 44 blue-collar married basic.4y unknown yes no cellular 1 999 0 nonexistent 1.4 93.444 -36.1 4.963 5228.1 0
1 1 1 53 technician married unknown no no no cellular 1 999 0 nonexistent -0.1 93.200 -42.0 4.021 5195.8 0
2 2 2 28 management single university.degree no yes no cellular 3 6 2 success -1.7 94.055 -39.8 0.729 4991.6 1
3 3 3 39 services married high.school no no no cellular 2 999 0 nonexistent -1.8 93.075 -47.1 1.405 5099.1 0
4 4 4 55 retired married basic.4y no yes no cellular 1 3 1 success -2.9 92.201 -31.4 0.869 5076.2 1

5 rows × 23 columns

MODEL BEZ ZBILANSOWANIA ZBIORÓW ———————————–

Skalowanie standardowe tylko dla wartości dyskretnych

Wybieram kolumny tekstowe, dyskretne, do głębszej analizy. Lepsze było to wybieranie dyskretne i ciągłe.

In [3]:
encoding_list = ['job', 'marital', 'education', 'default', 'housing', 'loan',
       'contact', 'month', 'day_of_week','poutcome']
In [4]:
df[encoding_list] = df[encoding_list].apply(LabelEncoder().fit_transform)
In [5]:
df[encoding_list].head()
Out[5]:
job marital education default housing loan contact month day_of_week poutcome
0 1 1 0 1 2 0 0 1 2 1
1 9 1 7 0 0 0 0 7 0 1
2 4 2 6 0 2 0 0 4 2 2
3 7 1 3 0 0 0 0 0 0 1
4 5 1 0 0 2 0 0 1 0 2

Tworzymy zestaw treningowy i zestaw testowy, budujemy model

In [6]:
y = df['y']
X = df.drop('y', axis=1) 

Złoty podział zioru na testowy i treningowy

In [7]:
from sklearn.model_selection import train_test_split 
Xtrain, Xtest, ytrain, ytest = train_test_split(X,y, test_size=0.33, stratify = y, random_state = 148)

wielkości zbiorów

In [8]:
print ('Zbiór X treningowy: ',Xtrain.shape)
print ('Zbiór X testowy:    ', Xtest.shape)
print ('Zbiór y treningowy: ', ytrain.shape)
print ('Zbiór y testowy:    ', ytest.shape)
Zbiór X treningowy:  (27595, 22)
Zbiór X testowy:     (13593, 22)
Zbiór y treningowy:  (27595,)
Zbiór y testowy:     (13593,)

Dane dyskretne są zdygitalizowane

In [9]:
Xtrain.head(4)
Out[9]:
Unnamed: 0 Unnamed: 0.1 age job marital education default housing loan contact duration campaign pdays previous poutcome emp_var_rate cons_price_idx cons_conf_idx euribor3m nr_employed
24697 24697 24697 49 1 1 2 1 0 0 1 222 9 999 0 1 1.4 94.465 -41.8 4.959 5228.1
25855 25855 25855 38 9 0 6 1 0 0 0 125 3 999 0 1 1.4 93.444 -36.1 4.963 5228.1
23236 23236 23236 42 0 0 6 0 0 0 1 26 4 999 0 1 1.4 94.465 -41.8 4.959 5228.1
13812 13812 13812 58 1 1 5 1 0 2 1 25 1 999 0 1 1.4 94.465 -41.8 4.866 5228.1

4 rows × 22 columns

Random Forest Classifier

In [10]:
from sklearn import model_selection
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import make_pipeline
In [11]:
forestVC = RandomForestClassifier (random_state = 1, 
                                  n_estimators = 750, 
                                  max_depth = 15, 
                                  min_samples_split = 5, min_samples_leaf = 1) 
modelF = forestVC.fit(Xtrain, ytrain)
y_predF = modelF.predict(Xtest)

Blok oceny jakości modelu Random Forest Classifier

Podstawienie do wzoru
In [12]:
ypred = modelF.predict(Xtest)
In [13]:
from sklearn.metrics import classification_report, confusion_matrix
from sklearn import metrics

co_matrix = metrics.confusion_matrix(ytest, ypred)
co_matrix
Out[13]:
array([[11692,   370],
       [  797,   734]], dtype=int64)
In [14]:
print(classification_report(ytest, ypred)) 
              precision    recall  f1-score   support

           0       0.94      0.97      0.95     12062
           1       0.66      0.48      0.56      1531

   micro avg       0.91      0.91      0.91     13593
   macro avg       0.80      0.72      0.75     13593
weighted avg       0.91      0.91      0.91     13593

In [15]:
print("Accuracy:   ",np.round(metrics.accuracy_score(ytest, ypred), decimals=2))
print("Precision:  ",np.round(metrics.precision_score(ytest, ypred), decimals=2))
print("Recall:     ",np.round(metrics.recall_score(ytest, ypred), decimals=2))
print("F1 score:   ",np.round(metrics.f1_score(ytest, ypred), decimals=2))
Accuracy:    0.91
Precision:   0.66
Recall:      0.48
F1 score:    0.56

Analiza poziomu zbilansowania zmiennej wynikowej

In [16]:
df.y.value_counts(dropna = False, normalize=True)
Out[16]:
0    0.887346
1    0.112654
Name: y, dtype: float64
In [17]:
print("ytrain = 0: ", sum(ytrain == 0))
print("ytrain = 1: ", sum(ytrain == 1))
ytrain = 0:  24486
ytrain = 1:  3109
In [18]:
Proporcja = sum(ytrain == 0) / sum(ytrain == 1) 
Proporcja = np.round(Proporcja, decimals=0)
Proporcja = Proporcja.astype(int)
Proporcja
Out[18]:
8

Na jedną daną sybskrypcje przypada 8 nieprzedłużonych subskrypcji. Powiększamy liczbę próbek niezależnych.

In [19]:
ytrain_pos_OVS = pd.concat([ytrain[ytrain==1]] * Proporcja, axis = 0) 
ytrain_pos_OVS.count()
Out[19]:
24872

Ilość zmiennych wynikowych: (1) zwiększyła się do liczby 24872
Mamy już wektor zmiennych wynikowych y, teraz trzeba zwiększyć liczbę zmiennych niezależnych.

Powiększamy liczbę próbek zmiennych niezależnych X
In [20]:
Xtrain_pos_OVS = pd.concat([Xtrain.loc[ytrain==1, :]] * Proporcja, axis = 0)
In [21]:
Xtrain_pos_OVS.age.count()
Out[21]:
24872
Powiększyliśmy ilość zmiennych gdzie wynik przedłużenia subskrypcji jest równy 1.

Teraz mamy tą samą liczbę wierszy zmiennych wynikowych i zmiennych niezależnych.

Teraz wprowadzamy nowe, dodatkowe zmienne 1 do zbioru treningowego.

In [22]:
ytrain_OVS = pd.concat([ytrain, ytrain_pos_OVS], axis = 0).reset_index(drop = True)
Xtrain_OVS = pd.concat([Xtrain, Xtrain_pos_OVS], axis = 0).reset_index(drop = True)

Sprawdzamy ilość wierszy w zbiorach przed i po oversampling

In [23]:
print("ilość elementów w zbiorze Xtrain:     ", Xtrain.age.count())
print("ilość elementów w zbiorze Xtrain_OVS: ", Xtrain_OVS.age.count())
print("ilość elementów w zbiorze ytrain:     ", ytrain.count())
print("ilość elementów w zbiorze ytrain_OVS: ", ytrain_OVS.count())
ilość elementów w zbiorze Xtrain:      27595
ilość elementów w zbiorze Xtrain_OVS:  52467
ilość elementów w zbiorze ytrain:      27595
ilość elementów w zbiorze ytrain_OVS:  52467
Teraz podstawiamy nowy zbiór testowy oversampling do siatki grid według tej same formuły, którą użyliśmy wcześniej.
In [24]:
forestVC = RandomForestClassifier (random_state = 1, 
                                  n_estimators = 750, 
                                  max_depth = 15, 
                                  min_samples_split = 5, min_samples_leaf = 1) 
modelF = forestVC.fit(Xtrain_OVS, ytrain_OVS)
Podstawienie do wzoru
In [25]:
ypred = modelF.predict(Xtest)
In [26]:
from sklearn.metrics import classification_report, confusion_matrix
from sklearn import metrics

co_matrix = metrics.confusion_matrix(ytest, ypred)
co_matrix
Out[26]:
array([[10875,  1187],
       [  268,  1263]], dtype=int64)
In [27]:
print(classification_report(ytest, ypred)) 
              precision    recall  f1-score   support

           0       0.98      0.90      0.94     12062
           1       0.52      0.82      0.63      1531

   micro avg       0.89      0.89      0.89     13593
   macro avg       0.75      0.86      0.79     13593
weighted avg       0.92      0.89      0.90     13593

In [28]:
print("Accuracy:    ",np.round(metrics.accuracy_score(ytest, ypred), decimals=2))
print("Precision:   ",np.round(metrics.precision_score(ytest, ypred), decimals=2))
print("Recall:      ",np.round(metrics.recall_score(ytest, ypred), decimals=2))
print("F1 score:    ",np.round(metrics.f1_score(ytest, ypred), decimals=2))
Accuracy:     0.89
Precision:    0.52
Recall:       0.82
F1 score:     0.63
Wynik modelu Random Forest Classifier przed Oversampling
  • Accuracy: 0.91
  • Precision: 0.66
  • Recall: 0.48
  • F1 score: 0.56

Wynik kodelu po Oversampling

  • Accuracy: 0.89
  • Precision: 0.52
  • Recall: 0.82
  • F1 score: 0.63