Conglomerate of the models #2: Goes using the oversampling method make any sense?

290520200955

Trident project part: conglomerate of the models

When we have a very unbalanced set, each classification model is good – that is, it has excellent parameters. Unfortunately, it depends on whether it classifies a collector’s collection or not. Today we will check whether the application of oversampling is an effective method. Starting this experiment today I had mixed experiences. I do not know what will come out we start the experiment.

In [1]:
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import warnings 
from sklearn.ensemble import BaggingClassifier
from simple_colors import * 
from prettytable import PrettyTable

warnings.filterwarnings("ignore")

%matplotlib inline

df= pd.read_csv('/home/wojciech/Pulpit/1/Stroke_Prediction.csv')
print(df.shape)
print()
print(df.columns)
df.head(3)
(43400, 12)

Index(['ID', 'Gender', 'Age_In_Days', 'Hypertension', 'Heart_Disease',
       'Ever_Married', 'Type_Of_Work', 'Residence', 'Avg_Glucose', 'BMI',
       'Smoking_Status', 'Stroke'],
      dtype='object')
Out[1]:
ID Gender Age_In_Days Hypertension Heart_Disease Ever_Married Type_Of_Work Residence Avg_Glucose BMI Smoking_Status Stroke
0 31153 Male 1104.0 0 0 No children Rural 95.12 18.0 NaN 0
1 30650 Male 21204.0 1 0 Yes Private Urban 87.96 39.2 never smoked 0
2 17412 Female 2928.0 0 0 No Private Urban 110.89 17.6 NaN 0

Zmniejszacz próbki

In [2]:
df = df.sample(frac = 1.0, random_state=10) 
df.shape
Out[2]:
(43400, 12)

Tool for automatic coding of discrete variables

obraz.png

In [3]:
a,b = df.shape     #<- ile mamy kolumn
b

print('DISCRETE FUNCTIONS CODED')
print('------------------------')
for i in range(1,b):
    i = df.columns[i]
    f = df[i].dtypes
    if f == np.object:
        print(i,"---",f)   
    
        if f == np.object:
        
            df[i] = pd.Categorical(df[i]).codes
        
            continue
DISCRETE FUNCTIONS CODED
------------------------
Gender --- object
Ever_Married --- object
Type_Of_Work --- object
Residence --- object
Smoking_Status --- object
In [4]:
df.fillna(7777, inplace=True)
In [5]:
X = df.drop('Stroke', axis=1) 
y = df['Stroke']  

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=123,stratify=y)

X_test = X_test.values
y_test = y_test.values
X_train = X_train.values
y_train = y_train.values

Oversampling obraz.png

In [6]:
def oversampling(ytrain, Xtrain):
    import matplotlib.pyplot as plt
    
    global Xtrain_OV
    global ytrain_OV

    calss1 = np.round((sum(ytrain == 1)/(sum(ytrain == 0)+sum(ytrain == 1))),decimals=2)*100
    calss0 = np.round((sum(ytrain == 0)/(sum(ytrain == 0)+sum(ytrain == 1))),decimals=2)*100
    
    print("y = 0: ", sum(ytrain == 0),'-------',calss0,'%')
    print("y = 1: ", sum(ytrain == 1),'-------',calss1,'%')
    print('--------------------------------------------------------')
    
    ytrain.value_counts(dropna = False, normalize=True).plot(kind='pie',title='Before oversampling')
    plt.show
    print()
    
    Proporcja = sum(ytrain == 0) / sum(ytrain == 1)
    Proporcja = np.round(Proporcja, decimals=0)
    Proporcja = Proporcja.astype(int)
       
    ytrain_OV = pd.concat([ytrain[ytrain==1]] * Proporcja, axis = 0) 
    Xtrain_OV = pd.concat([Xtrain.loc[ytrain==1, :]] * Proporcja, axis = 0)
    
    ytrain_OV = pd.concat([ytrain, ytrain_OV], axis = 0).reset_index(drop = True)
    Xtrain_OV = pd.concat([Xtrain, Xtrain_OV], axis = 0).reset_index(drop = True)
    
    Xtrain_OV = pd.DataFrame(Xtrain_OV)
    ytrain_OV = pd.DataFrame(ytrain_OV)
    

    
    print("Before oversampling Xtrain:     ", Xtrain.shape)
    print("Before oversampling ytrain:     ", ytrain.shape)
    print('--------------------------------------------------------')
    print("After oversampling Xtrain_OV:  ", Xtrain_OV.shape)
    print("After oversampling ytrain_OV:  ", ytrain_OV.shape)
    print('--------------------------------------------------------')
    
    
    ax = plt.subplot(1, 2, 1)
    ytrain.value_counts(dropna = False, normalize=True).plot(kind='pie',title='Before oversampling')
    plt.show
    
       
    kot = pd.concat([ytrain[ytrain==1]] * Proporcja, axis = 0)
    kot = pd.concat([ytrain, kot], axis = 0).reset_index(drop = True)
    ax = plt.subplot(1, 2, 2)
    kot.value_counts(dropna = False, normalize=True).plot(kind='pie',title='After oversampling')
    plt.show
In [7]:
oversampling(y_train, X_train)
y = 0:  34094 ------- 98.0 %
y = 1:  626 ------- 2.0 %
--------------------------------------------------------

Before oversampling Xtrain:      (34720, 11)
Before oversampling ytrain:      (34720,)
--------------------------------------------------------
After oversampling Xtrain_OV:   (68524, 11)
After oversampling ytrain_OV:   (68524, 1)
--------------------------------------------------------

I used three models of GaussianNB, LogisticRegression, CatBoostClassifier in their basic version without oversamplin and with oversampling. We will see what differences in the minority set classification the oversampling method gives.obraz.png

I get rid of one dimension from the ytrain_OV set so that the set is similar to y_test.

In [8]:
print(Xtrain_OV.shape)
print(ytrain_OV.shape)
ytrain_OV = ytrain_OV['Stroke']
print(ytrain_OV.shape)
(68524, 11)
(68524, 1)
(68524,)
In [9]:
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble import RandomForestClassifier
from lightgbm import LGBMClassifier
from sklearn.ensemble import RandomForestClassifier
from catboost import CatBoostClassifier
from sklearn.svm import SVC 
from sklearn.linear_model import LassoCV



NBC = GaussianNB() 
LRE = LogisticRegression(solver='lbfgs')
GBC = GradientBoostingClassifier()
RFC = RandomForestClassifier()
LGBM = LGBMClassifier() 
CBC = CatBoostClassifier(verbose=0, n_estimators=100)


classifiers_A = [NBC,LRE,GBC,RFC,LGBM,CBC]
name = ['NBC','LRE','GBC','RFC','LGBM','CBC']

#for cls in classifiers_A:
#    cls.fit(X_train, y_train)
    
classifiers_OV = [NBC,LRE,GBC,RFC,LGBM,CBC]
name2 = ['NBC_OV','LRE_OV','GBC_OV','RFC_OV','LGBM_OV','CBC_OV']

#for t in classifiers_OV:
#    t.fit(Xtrain_OV, ytrain_OV)    
In [10]:
def Recall_Precision(six_classifiers,name, X_train, y_train,X_test,y_test):

    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn import metrics
    from sklearn.metrics import classification_report, confusion_matrix
    from sklearn.metrics import confusion_matrix, log_loss, auc, roc_curve, roc_auc_score, recall_score, precision_recall_curve
    from sklearn.metrics import make_scorer, precision_score, fbeta_score, f1_score, classification_report
    from sklearn.metrics import accuracy_score
    from mlxtend.plotting import plot_learning_curves
    from prettytable import PrettyTable
    #from simple_colors import * 

    for cls in six_classifiers:
        cls.fit(X_train, y_train)
    
    
    Recall_Training = ['Recall Training: ']
    Precision_Training = ['Precision Training: ']
    Recall_Test= ['Recall Test: ']
    Precision_Test = ['Precision Test: ']

    def compute_metric2(model):

        Recall_Training = np.round(recall_score(y_train, model.predict(X_train)), decimals=3)
        Precision_Training = np.round(precision_score(y_train, model.predict(X_train)), decimals=3)
        Recall_Test = np.round(recall_score(y_test, model.predict(X_test)), decimals=3) 
        Precision_Test = np.round(precision_score(y_test, model.predict(X_test)), decimals=3)
        
        return Recall_Training, Precision_Training, Recall_Test, Precision_Test

    for cls in six_classifiers:

        results = compute_metric2(cls)
        Recall_Training.append(results[0])
        Precision_Training.append(results[1])
        Recall_Test.append(blue(results[2],'bold'))
        Precision_Test.append((blue(results[3],'bold')))
   
    
    t = PrettyTable(['Name', name[0],name[1],name[2],name[3],name[4],name[5]])
    t.add_row(Recall_Training)
    t.add_row(Precision_Training)
    t.add_row(Recall_Test)
    t.add_row(Precision_Test)

    print(t)
    print(green('RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem','italic'))
    print(green('PRECISION - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi zdiagnozowanyc fałszywie lub prawdziwie jako chorych','italic'))

In confusuin_matrix we have 4 states to consider. These values ​​are presented in the form of a matrix in which the Y axis shows the real classes, while the X axis shows the predicted classes. Because we are considering a medical case, let’s imagine that we are analyzing if someone has COVID-19.

True Positive – patients who were not COVIT-19 sufferers were diagnosed with COVID-19. For the GaussianNB model it was 8246 patients.

True Negative – patients who have bulls with COVIT-19, and the model has detected that they are sick. There were 34 such people for the GaussianNB model.

False Positive (Test false) – The test showed that patients did not have COVIT-19 and they were sick. This is the first type of error. For GaussainNB there were 277 people.

False Negative – This is the number of people who are healthy (marked as 0), however the model indicates that they are people with COVIT-19. This is a second type of error. For Gaussian there were 123 people.

In confusuin_matrix we have 4 states to consider. These values ​​are presented in the form of a matrix in which the Y axis shows the real classes, while the X axis shows the predicted classes. Because we are considering a medical case, let’s imagine that we are analyzing if someone has COVID-19.

True Positive – patients who were not COVIT-19 have been diagnosed with COVID-19.
For the GaussianNB model it was 8246 patients.

True Negative – patients who have bulls with COVIT-19, and the model has detected that they are sick.
There were 34 such people for the GaussianNB model.

False Positive – Test showed that patients did not have COVIT-19 and they were sick. This is the first type of error. For GaussainNB there were 277 people.

False Negative – This is the number of people who are healthy (marked as 0) but the model has shown that they are people with COVIT-19. This is a second type of error. For Gaussian there were 123 people.

It is easy to see that when the model without oversampling is used, the number of detected COVIT-19 patients is catastrophically low. Confusion_matrix operates only on test sets. In our case, 157 patients with COVIT-19 have 8523 healthy people.
As medical personnel, we are interested in the detection of patients. Without oversampling, Gaussian NB was detected by 34 patients out of 157 COVID-19 patients, the remaining models were correctly detected by one or two patients. That is, in the case of LogisticRegression among 157 COVIT-19 patients, one was detected as a patient (True-Negative) while 156 really COVID-19 patients were considered healthy (type II error). Furthermore, 3 healthy people were considered to be COVID-19 patients. Similarly tragic quality occurred in subsequent classification models.

After balancing the classes by applying oversampling, the COVID-19 detection level has improved significantly.
For the aforementioned LogisticRegression model, the model detected 121 patients out of 157 possible (True-Negative). However, he scared 3701 people saying that they have COVID even though they were healthy, i.e. this model caught the patients (True-Negative) while it indicated 3701 as sick although they were healthy, there were 8523 healthy (type I error). Normally, the model would fly far out the window. The discussed LogisticRegression model also found 36 patients healthy (type II error) after oversampling, i.e. it caused further infections, because these people went to work normally. the other models did better, but the results after oversampling are still not satisfactory.

obraz.png

W confusuin_matrix mamy 4 stany do rozważenia. Wartości te są przedstawione w postaci macierzy, w której oś Y pokazuje prawdziwe klasy, podczas gdy oś X pokazuje przewidywane klasy. Ponieważ rozpatrujemy przypadek medyczny wyobraźmy sobie, że analizujemy czy ktoś jest chory na COVID-19.

True Positive – pacjęci, którzy nie byli chorzy na COVIT-19, zostali zdiagnozowani że nie są chodzy na COVID-19.
Dla modelu GaussianNB było to 8246 pacjętów.

True Negative – pacjęci, którzy byki chorzy na COVIT-19, i model wykrył, że są chorzy.
Dla modelu GaussianNB było takich osób 34.

False Positive (czyli fałszywie zdrowy) – Test wykazał, że pacjęci nie mmieli COVIT-19 a oni byli chorzy. Jest to błąd pierwszego typu. Dla GaussainNB było to 277 osób.

False Negative (czyli fałszywie chory)- To liczba osób które są zdrowe (oznaczone jako 0) jednak model wskazał że są to osoby chore na COVIT-19. Jest to błąd drugiego rodzaju. Dla Gaussian były to 123 osoby.

Latwo zauważyć, że w przypadku użycia modelu bez oversampling ilość wykrytych chorych na COVIT-19 jest katastrofalnie niska. Confusion_matrix operuje wyłacznie na zbiorach testowych. W naszym przypadku chorych na COVIT-19 jest w 157 zaś zdrowych jest 8523 osoby.
Nas jako personel medyczny interesuje wykrywalność chorych. Bez oversampling Gaussian NB wykryło 34 chorych na 157 chorych na COVID-19, pozostałe modele wykryły prawidłowo po jednym lub dwóch pacjętów. Czyli w przypadku na przykład LogisticRegression z pośród 157 chorych na COVIT-19 jeden został wykryty jako chory (True-Negative) natomiast 156 naprawde chorych na COVID-19 zostało uznanych za zdrowych (błąd II rodzaju). Co więcej, 3 osoby zdrowe uznane zostały za chorych na COVID-19. Podobnie tragiczna jakość wystąpiła w kolejnych modelach klasyfikacji.

Po zbilansowaniu klas przez zastosowanie oversampling poziom wykrywalności COVID-19 znacznie się poprawił.
Dla wspomnianego modelu LogisticRegression model wykrył 121 chorych na 157 możliwych (True-Negative). Natomiast wystraszył 3701 osób mówiąc że mają COVID mimo, że byli zdrowi, czyli ten model wyłapał chorych (True-Negative) natomiast wskazał, 3701 jako chorych mimo, że byli zdrowi, zdrowych łącznie było 8523 (bład I typu). W normalnej sytuacji model wyleciałby daleko za okno. Omawiany model LogisticRegression po oversampling uznał też 36 chorych za zdrowych (błąd II typu) czyli przysporzył kolejnych zarażeń, bo ludzie ci poszli normalnie do pracy. pozostałe modele lepiej poroadziły sobie lecz wyniki po oversampling wciąż nie należą do zadowalających.

In [11]:
y_test.value_counts()
Out[11]:
0    8523
1     157
Name: Stroke, dtype: int64
In [12]:
def confusion_matrix(six_classifiers,name, X_train, y_train,X_test,y_test):

    from sklearn.metrics import plot_confusion_matrix
    
    for cls in six_classifiers:
        cls.fit(X_train, y_train) 
    
    fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(14,7))
    target_names = ['0','1']


    for cls, ax in zip(six_classifiers, axes.flatten()):
        plot_confusion_matrix(cls, 
                              X_test, 
                              y_test, 
                              ax=ax, 
                              cmap='Blues',
                             display_labels=target_names,values_format='')
        ax.title.set_text(type(cls).__name__)
        ax.title.set_color('blue')
    
    plt.tight_layout()  
    plt.show()
In [13]:
confusion_matrix(classifiers_A,name, X_train, y_train,X_test,y_test)

After oversampling

In [14]:
confusion_matrix(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)

Recall przed oversampling

W naszym przykładzie Recall oznacza odsetek chorych, u których rozpoznano chorobę (true-positive) z pośród wszytkich chorych.

$ Recall = displaystylefrac{tp}{tp+fn} $

Bez oversampling mamy bardzo niezbilansowane klasy, w naszym przypadku zdrowi oznaczeni jako 0 stanowią 98% populacji zaś chorzy oznaczeni jako 1 stanowi zaledwie 2% populacji. Poziom Ricall dla modelu GaussianNB oblicza się ze wzoru:

$ Recall = displaystylefrac{34}{34+123}=0.217 $

Dla modelu LogisticRegression:

$ Recall = displaystylefrac{1}{1+154}=0.0064 $

Jak widać Recall pokazuje jaki procent chorych wskazał model jako chorych z pośród wszystkich chorych. Bez oversampling, przy mocno niezbilansowanym zbiorze wynik jest bardzo zły.

Recall po oversampling

Po zbilansowaniu próby przez oversampling otrzymaliśmy znacznie lepsze wyniki recall. Dla modelu GaussianNB wynik ten wyniósł:

$ Recall = displaystylefrac{120}{120+37}=0.764 $

Dla LogisticRegression:

$ Recall = displaystylefrac{121}{121+31}=0.771 $

Precission przed oversampling

Pokazuje ile było wykrytych chorych (true-positive) w stosunku do wszystkich wskazanych przez model chorych, czyli w stosunku do sumy naprawde chorych wskazanych przez model (True-Positive) oraz zdrowych wskazanych przez model jako chorych (false-negative)

$ Precision = displaystylefrac{tp}{tp+fp} $

Przed oversampling winik precision dla modelu GaussianNB bedzie wynosił:

$ Precision = displaystylefrac{34}{34+277} = 0.109$

Dla modelu LogisticRegression:

$ Precision = displaystylefrac{1}{1+3} = 0.25$

Precission po oversampling

Dla modelu GaussianNB:

$ Precision = displaystylefrac{120}{120+1739} = 0.064$

Dla modelu LogisticRegression:

$ Precision = displaystylefrac{121}{121+3701} = 0.032$

Czyli modele są dalej beznadziejne. Wyobraźmy sobie przychodnie zdrowia, w której testy wskazały chorych na COVID-19. Jednak potam okazało sie, że na stu pacjętów jedynie 6% pacjętów było naprawde zarażonych wirusem.
Oversampling nie pomógł we wskaźniku precision. Oversampling spowodował znaczny wzrost czułości modeli.

I jeszcze jedno:
Zwiększenie recall powoduje zmniejszenie precission co zobaczymy na wykresach w dalszej części opracowania.

In [15]:
Recall_Precision(classifiers_A,name,X_train, y_train,X_test,y_test)
+----------------------+-------+-------+-------+-------+-------+-------+
|         Name         |  NBC  |  LRE  |  GBC  |  RFC  |  LGBM |  CBC  |
+----------------------+-------+-------+-------+-------+-------+-------+
|  Recall Training:    | 0.196 | 0.003 | 0.032 | 0.998 |  0.22 | 0.072 |
| Precision Training:  | 0.104 | 0.118 |  0.87 |  1.0  | 0.986 | 0.938 |
|    Recall Test:      | 0.217 | 0.006 | 0.006 | 0.006 | 0.019 | 0.013 |
|   Precision Test:    | 0.109 |  0.25 |  0.2  |  1.0  | 0.273 |  0.5  |
+----------------------+-------+-------+-------+-------+-------+-------+
RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem
PRECISION - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi zdiagnozowanyc fałszywie lub prawdziwie jako chorych
In [16]:
Recall_Precision(classifiers_OV,name2,Xtrain_OV, ytrain_OV,X_test,y_test)
+----------------------+--------+--------+--------+--------+---------+--------+
|         Name         | NBC_OV | LRE_OV | GBC_OV | RFC_OV | LGBM_OV | CBC_OV |
+----------------------+--------+--------+--------+--------+---------+--------+
|  Recall Training:    | 0.762  | 0.791  | 0.874  |  1.0   |  0.994  | 0.998  |
| Precision Training:  | 0.789  | 0.645  |  0.8   |  1.0   |  0.896  | 0.923  |
|    Recall Test:      | 0.764  | 0.771  | 0.758  | 0.019  |  0.529  | 0.465  |
|   Precision Test:    | 0.065  | 0.032  |  0.06  | 0.333  |  0.072  |  0.08  |
+----------------------+--------+--------+--------+--------+---------+--------+
RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem
PRECISION - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi zdiagnozowanyc fałszywie lub prawdziwie jako chorych
In [17]:
def classification_score(six_classifiers,name, X_train, y_train,X_test,y_test):

    from sklearn.metrics import precision_recall_fscore_support as score

    Precision_0 = ['Precision_0: ']
    Precision_1 = ['Precision_1: ']
    Recall_0 = ['Recall_0: ']
    Recall_1 = ['Recall_1: ']
    f1_score_0 = ['f1-score_0: ']
    f1_score_1 = ['f1-score_1: ']
    Support_0 = ['Support_0: ']
    Support_1 = ['Support_1: ']

    for cls in six_classifiers:
        cls.fit(X_train, y_train)
        
    
    def compute_metric4(model):

        precision, recall, fscore, support = score(y_test, model.predict(X_test))
    
        Precision_0 = np.round(precision[:1],decimals=3).item()
        Precision_1 = np.round(precision[1:],decimals=3).item()
        Recall_0 = np.round(recall[:1],decimals=3).item()
        Recall_1 = np.round(recall[1:],decimals=3).item()
        f1_score_0 = np.round(fscore[:1],decimals=3).item()
        f1_score_1 = np.round(fscore[1:],decimals=3).item()
        Support_0 = np.round(support[:1],decimals=3).item()
        Support_1 = np.round(support[1:],decimals=3).item()
        
        return Precision_0, Precision_1, Recall_0, Recall_1, f1_score_0, f1_score_1, Support_0, Support_1

    for cls in six_classifiers:

        results = compute_metric4(cls)
        Precision_0.append(results[0])
        Precision_1.append(blue(results[1],'bold'))
        Recall_0.append(results[2])
        Recall_1.append(blue(results[3],'bold'))
        f1_score_0.append(results[4])
        f1_score_1.append(blue(results[5],'bold'))
        Support_0.append(results[6])
        Support_1.append(blue(results[7],'bold'))
         

    t = PrettyTable(['Name', name[0],name[1],name[2],name[3],name[4],name[5]])
    t.add_row(Precision_0)
    t.add_row(Precision_1)
    t.add_row(Recall_0)
    t.add_row(Recall_1)
    t.add_row(f1_score_0)
    t.add_row(f1_score_1)
    t.add_row(Support_0)
    t.add_row(Support_1)


    print(t)
    print(green('RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem','italic'))
    print(green('PRECISION - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi zdiagnozowanyc fałszywie lub prawdziwie jako chorych','italic'))

Analiza recall – precision dla osób zdrowych

In our example, we examine the quality of the classification for the detection of COVID_19 patients. It is less important to say that healthy people are really healthy. Due to the fact that only 2% of patients are sick, even if they consider everyone (even the sick) to be healthy, they will have a high recall and a very high precision ratio.
Such information does not lead to anything, below are the calculations for healthy for the GaussianNB model.

W naszym przykładzie badamy jakość kalsyfikacji dla wykrywania chorych na COVID_19. Mniejsze znaczenie ma stwierdzdenie, że zdrowi są naprawde zdrowi. Przez to, że chorych jest zaledwie 2%, modele nawet jak uznają wszystkich (nawet chorych) za zdrowych będą miały wysoki recall i bardzo wysoki wskaźnik precision.
Taka informacja do niczego nie prowadzi, poniżej znajdują się kalkulacje dla zdrowych dla modelu GaussianNB.

Przed oversampling dla modelu GaussianNB:

$ Precision = displaystylefrac{8246}{8246+123} = 0.985$

In [18]:
from sklearn.metrics import confusion_matrix

NBC = NBC.fit(X_train,y_train)
confusion_matrix(y_test, NBC.predict(X_test))
Out[18]:
array([[8246,  277],
       [ 123,   34]])

Po oversampling dla modelu GaussianNB:

$ Precision = displaystylefrac{6784}{6784+37} = 0.995$

In [19]:
from sklearn.metrics import confusion_matrix

NBC_OV = NBC.fit(Xtrain_OV, ytrain_OV)
confusion_matrix(y_test, NBC_OV.predict(X_test))
Out[19]:
array([[6784, 1739],
       [  37,  120]])
In [20]:
classification_score(classifiers_A,name, X_train, y_train,X_test,y_test)
+---------------+-------+-------+-------+-------+-------+-------+
|      Name     |  NBC  |  LRE  |  GBC  |  RFC  |  LGBM |  CBC  |
+---------------+-------+-------+-------+-------+-------+-------+
| Precision_0:  | 0.985 | 0.982 | 0.982 | 0.982 | 0.982 | 0.982 |
| Precision_1:  | 0.109 |  0.25 |  0.2  |  1.0  | 0.273 |  0.5  |
|   Recall_0:   | 0.967 |  1.0  |  1.0  |  1.0  | 0.999 |  1.0  |
|   Recall_1:   | 0.217 | 0.006 | 0.006 | 0.006 | 0.019 | 0.013 |
|  f1-score_0:  | 0.976 | 0.991 | 0.991 | 0.991 | 0.991 | 0.991 |
|  f1-score_1:  | 0.145 | 0.012 | 0.012 | 0.013 | 0.036 | 0.025 |
|  Support_0:   |  8523 |  8523 |  8523 |  8523 |  8523 |  8523 |
|  Support_1:   |  157  |  157  |  157  |  157  |  157  |  157  |
+---------------+-------+-------+-------+-------+-------+-------+
RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem
PRECISION - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi zdiagnozowanyc fałszywie lub prawdziwie jako chorych
In [21]:
classification_score(classifiers_OV,name2,Xtrain_OV, ytrain_OV,X_test,y_test)
+---------------+--------+--------+--------+--------+---------+--------+
|      Name     | NBC_OV | LRE_OV | GBC_OV | RFC_OV | LGBM_OV | CBC_OV |
+---------------+--------+--------+--------+--------+---------+--------+
| Precision_0:  | 0.995  | 0.993  | 0.994  | 0.982  |   0.99  | 0.989  |
| Precision_1:  | 0.065  | 0.032  |  0.06  |  0.5   |  0.072  |  0.08  |
|   Recall_0:   | 0.796  | 0.566  | 0.781  | 0.999  |  0.875  | 0.902  |
|   Recall_1:   | 0.764  | 0.771  | 0.758  | 0.032  |  0.529  | 0.465  |
|  f1-score_0:  | 0.884  | 0.721  | 0.875  | 0.991  |  0.929  | 0.944  |
|  f1-score_1:  | 0.119  | 0.061  | 0.111  |  0.06  |  0.127  | 0.137  |
|  Support_0:   |  8523  |  8523  |  8523  |  8523  |   8523  |  8523  |
|  Support_1:   |  157   |  157   |  157   |  157   |   157   |  157   |
+---------------+--------+--------+--------+--------+---------+--------+
RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem
PRECISION - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi zdiagnozowanyc fałszywie lub prawdziwie jako chorych

Miara f1 jest miarą łaczącą precision i recall. Jet to w przybliżeniu średni z tych dwóch wskaźników.

$ F1 = 2timesdisplaystylefrac{precisiontimes{recall}}{precision+{recall}} $

Przed oversampling dla modelu GaussianNB dla klasy 1:

$ F1 = 2timesdisplaystylefrac{0.109times{0.217}}{0.109+{0.217}}= 0.1451$

Po oversampling dla modelu GaussianNB dla klasy 1:

$ F1 = 2timesdisplaystylefrac{0.065times{0.764}}{0.065+{0.764}}= 0.1198$

Jak widać dla modelu GaussianNB zastosowanie oversampling pogorszył wynik wskaźnika F1.

In [22]:
def AUC_score(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    from sklearn import metrics

    for cls in six_classifiers:
        cls.fit(X_train, y_train)    
    
    
    AUC_train = ['AUC_train: ']
    AUC_test = ['AUC_test: ']

    def compute_metric(model):

        auc_train = np.round(metrics.roc_auc_score(y_train,model.predict_proba(X_train)[:,1]),decimals=3)
        auc_test = np.round(metrics.roc_auc_score(y_test,model.predict_proba(X_test)[:,1]),decimals=3)

        return auc_train, auc_test

    for cls in six_classifiers:

        results = compute_metric(cls)
        AUC_train.append(results[0])
        AUC_test.append(results[1])


    t = PrettyTable(['Name', name[0],name[1],name[2],name[3],name[4],name[5]])
    t.add_row(AUC_train)
    t.add_row(AUC_test)

    print(t)

Wielkość pola pod krzywą AUC jest wysoki zarówno przed jak i po oversampling. Oversampling nieznacznie wpłynął na poprawę jakości klasyfikacji modeli.

In [23]:
AUC_score(classifiers_A,name, X_train, y_train,X_test,y_test)
+-------------+-------+-------+-------+-------+-------+-------+
|     Name    |  NBC  |  LRE  |  GBC  |  RFC  |  LGBM |  CBC  |
+-------------+-------+-------+-------+-------+-------+-------+
| AUC_train:  | 0.849 | 0.662 | 0.901 |  1.0  |  0.99 |  0.92 |
|  AUC_test:  | 0.853 | 0.627 | 0.862 | 0.816 | 0.844 | 0.852 |
+-------------+-------+-------+-------+-------+-------+-------+
In [24]:
AUC_score(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
+-------------+--------+--------+--------+--------+---------+--------+
|     Name    | NBC_OV | LRE_OV | GBC_OV | RFC_OV | LGBM_OV | CBC_OV |
+-------------+--------+--------+--------+--------+---------+--------+
| AUC_train:  | 0.849  | 0.756  | 0.909  |  1.0   |  0.985  | 0.989  |
|  AUC_test:  | 0.853  |  0.75  | 0.852  | 0.817  |  0.832  | 0.804  |
+-------------+--------+--------+--------+--------+---------+--------+
In [25]:
def Accuracy_score(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    from sklearn.metrics import accuracy_score
    
    for cls in six_classifiers:
        cls.fit(X_train, y_train)     
    
    
    Accuracy_Training = ['Accuracy_Training: ']
    Accuracy_Test = ['Accuracy_Test: ']

    def compute_metric5(model):

        Accuracy_Training = np.round(accuracy_score(y_train, model.predict(X_train)), decimals=3)
        Accuracy_Test = np.round(accuracy_score(y_test, model.predict(X_test)), decimals=3)

        return  Accuracy_Training, Accuracy_Test

    for cls in six_classifiers:

        results = compute_metric5(cls)
        Accuracy_Training.append(results[0])
        Accuracy_Test.append(results[1])


    t = PrettyTable(['Name', 'GN','LogReg','GradBoos','RandFor','LGBM','CatBoost'])
    t.add_row(Accuracy_Training)
    t.add_row(Accuracy_Test)

    print(t)
In [26]:
Accuracy_score(classifiers_A,name, X_train, y_train,X_test,y_test)
+---------------------+-------+--------+----------+---------+-------+----------+
|         Name        |   GN  | LogReg | GradBoos | RandFor |  LGBM | CatBoost |
+---------------------+-------+--------+----------+---------+-------+----------+
| Accuracy_Training:  | 0.955 | 0.982  |  0.982   |   1.0   | 0.986 |  0.983   |
|   Accuracy_Test:    | 0.954 | 0.982  |  0.981   |  0.982  | 0.981 |  0.982   |
+---------------------+-------+--------+----------+---------+-------+----------+
In [27]:
Accuracy_score(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
+---------------------+-------+--------+----------+---------+-------+----------+
|         Name        |   GN  | LogReg | GradBoos | RandFor |  LGBM | CatBoost |
+---------------------+-------+--------+----------+---------+-------+----------+
| Accuracy_Training:  | 0.778 | 0.676  |  0.827   |   1.0   | 0.939 |  0.957   |
|   Accuracy_Test:    | 0.795 | 0.569  |  0.781   |  0.982  | 0.869 |  0.894   |
+---------------------+-------+--------+----------+---------+-------+----------+
In [28]:
def f1_score(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    from sklearn import metrics
    from sklearn.metrics import make_scorer, precision_score, fbeta_score, f1_score, classification_report
    
    for cls in six_classifiers:
        cls.fit(X_train, y_train) 
    
    classifiers = [NBC,LRE,GBC,RFC,LGBM,CBC]
    f1_score_macro = ['f1_score_macro:  ']
    f1_score_micro = ['f1_score_micro: ']

    def compute_metric6(model):

        f1_score_macro = np.round(metrics.f1_score(y_test, model.predict(X_test), average='macro'), decimals=3)
        f1_score_micro = np.round(metrics.f1_score(y_test, model.predict(X_test), average='micro'), decimals=3)

        return  f1_score_macro, f1_score_micro

    for cls in six_classifiers:

        results = compute_metric6(cls)
        f1_score_macro.append(results[0])
        f1_score_micro.append(results[1])


    t = PrettyTable(['Name', 'GN','LogReg','GradBoos','RandFor','LGBM','CatBoost'])
    t.add_row(f1_score_macro)
    t.add_row(f1_score_micro)

    print(t)
In [29]:
f1_score(classifiers_A,name, X_train, y_train,X_test,y_test)
+-------------------+-------+--------+----------+---------+-------+----------+
|        Name       |   GN  | LogReg | GradBoos | RandFor |  LGBM | CatBoost |
+-------------------+-------+--------+----------+---------+-------+----------+
| f1_score_macro:   | 0.561 | 0.502  |  0.502   |  0.502  | 0.513 |  0.508   |
|  f1_score_micro:  | 0.954 | 0.982  |  0.982   |  0.982  | 0.981 |  0.982   |
+-------------------+-------+--------+----------+---------+-------+----------+
In [30]:
f1_score(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
+-------------------+-------+--------+----------+---------+-------+----------+
|        Name       |   GN  | LogReg | GradBoos | RandFor |  LGBM | CatBoost |
+-------------------+-------+--------+----------+---------+-------+----------+
| f1_score_macro:   | 0.502 | 0.391  |  0.493   |  0.519  | 0.528 |   0.54   |
|  f1_score_micro:  | 0.795 | 0.569  |  0.781   |  0.982  | 0.869 |  0.894   |
+-------------------+-------+--------+----------+---------+-------+----------+

False Positive Rate ( type I error)

Błąd powstaje kiedy przewidujemy coś, gdy tego nie ma. W naszym przykładzie będzie to ilość ludzi zdrowych uznanych przez model za chorych w stosunku do wszystkich zdrowych w populacji. Czyli procentowy udział ludzi zdrowych źle zdiagnozowanych w zbiorowisku ludzi zdrowych.
Czym mniejszy jest ten udział tym lepiej!

False Positive Rate można wyliczyć ze wzoru:

$ False Positive Rate = displaystylefrac{fp}{fp+tn}$

Przed oversampling dla modelu GaussianNB dla klasy 1:

$ False Positive Rate = displaystylefrac{277}{277+8246}=0.0325$

Po oversampling dla modelu GaussianNB dla klasy 1:

$ False Positive Rate = displaystylefrac{1739}{1739+6784}=0.2040$



$begin{matrix} tn & fp \ fn & tp
end{matrix}$

To samo można obliczyć za pomocą funkcji:

In [31]:
from sklearn.metrics import confusion_matrix

NBC = NBC.fit(X_train,y_train)
cm = confusion_matrix(y_test, NBC.predict(X_test))
tn, fp, fn, tp = cm.ravel()
print('tn: ',tn)
print('fp: ',fp)
print('fn: ',fn)
print('tp: ',tp)
print('------------------')
print(cm) 
tn:  8246
fp:  277
fn:  123
tp:  34
------------------
[[8246  277]
 [ 123   34]]
In [32]:
false_positive_rate = fp / (fp + tn)
false_positive_rate
Out[32]:
0.032500293323946966
In [33]:
NBC_OV = NBC.fit(Xtrain_OV,ytrain_OV)
cm_ov = confusion_matrix(y_test, NBC_OV.predict(X_test))
tn, fp, fn, tp = cm_ov.ravel()
print('tn: ',tn)
print('fp: ',fp)
print('fn: ',fn)
print('tp: ',tp)
print('------------------')
print(cm_ov)
tn:  6784
fp:  1739
fn:  37
tp:  120
------------------
[[6784 1739]
 [  37  120]]
In [34]:
false_positive_rate = fp / (fp + tn)
false_positive_rate
Out[34]:
0.20403613751026634

True Positive Rate (type II error) RECALL

W liczniku jest ilość chorych uznanych przez model za chorych. Wskaźnik mówi jaki jest procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem.

$ True Positive Rate = displaystylefrac{tp}{tp+fn}$

Przed oversampling dla modelu GaussianNB dla klasy 1:

$ True Positive Rate = displaystylefrac{34}{34+123} = 0.2165 $

Po oversampling dla modelu GaussianNB dla klasy 1:

$ True Positive Rate = displaystylefrac{120}{120+37} = 0.7643 $

Generalnie zbilansowanie populacji przez oversampling znacznie poprawiło zdolności klasyfikacyjne modeli co widać na przykładzie czerwonego punktu który wyemigrował w okolicę połowy krzywej ROC.
Podobnie poprawił sie nieznacznie rozmiar pola AUC.

In [35]:
????_????????_???? = tp / (tp + fn)
????_????????_????
Out[35]:
0.7643312101910829

False Negative Rate (type II error)

Wskaźnik ten można zdefiniować jako procentowy udział niewykrytych chorych (fn) w badanej populacji.
Czyli ilość niewykrytych chorych jest dzielona przez sume wszystkich faktycznie chorych. To coś w rodzaju recall lecz tu w liczbniku zamiast chorych wykrytych jest liczba chorych niewykrytych.

$ False Negative Rate = displaystylefrac{fn}{tp+fn}$

In [36]:
from sklearn.metrics import confusion_matrix

NBC = NBC.fit(X_train,y_train)
cm = confusion_matrix(y_test, NBC.predict(X_test))
tn, fp, fn, tp = cm.ravel()
print('tn: ',tn)
print('fp: ',fp)
print('fn: ',fn)
print('tp: ',tp)
print('------------------')
print(cm) 
tn:  8246
fp:  277
fn:  123
tp:  34
------------------
[[8246  277]
 [ 123   34]]
In [37]:
False_Negative_Rate = fn/(tp+fn)
False_Negative_Rate
Out[37]:
0.7834394904458599

True Negative Rate (Specificity)

W naszym przykładzie wykrywania chorych COVID-19 pokazuje nam, ilu ludzi zdrowych procentowo spośród wszystkich zdrowych sklasyfikowano jako zdrowych. Jak wypada 80% to znaczy że 20% ludzi zdrowych zostało niepotrzebnie wystraszonych, że są chorzy.

$ Specificity = displaystylefrac{tn}{tn+fp}$

In [38]:
def Type_error(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    from sklearn import metrics
    import simple_colors
    
    for cls in six_classifiers:
        cls.fit(X_train, y_train)    
    
    
    FPR = ['False_Positive_Rate:']
    TPR = ['True_Positive_Rate: ']
    FNR = ['False_Negative_Rate: ']
    SPEC = ['Specifity']

    def compute_metric(model):

        FPR = np.round(fp/(fp + tn),decimals=3)
        TPR = np.round(tp/(tp + fn),decimals=3)
        FNR = np.round(fn/(tp + fn),decimals=3)
        SPEC = np.round(tn/(tn + fp),decimals=3)

        return FPR,TPR,FNR,SPEC

    for cls in six_classifiers:

        cls.fit(X_train,y_train)
        cm = confusion_matrix(y_test, cls.predict(X_test))
        tn, fp, fn, tp = cm.ravel()
        
        kom =['1','2','3','4']
        
        
        results = compute_metric(cls)
        FPR.append(results[0])
        TPR.append(red(results[1],'bold'))
        FNR.append(results[2])
        SPEC.append(results[3])

    t = PrettyTable(['Name', name[0],name[1],name[2],name[3],name[4],name[5]])
    t.add_row(FPR)
    t.add_row(TPR)
    t.add_row(FNR)
    t.add_row(SPEC)

    print(t)
    
    print(black('False_Positive_Rate - procentowy udział ludzi zdrowych uznanych przez model za chorych w populacji ludzi zdrowych','italic'))
    print(red('True_Positive_Rate RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem','italic'))
    print(black('False_Negative_Rate - procentowy udział niewykrytych chorych w populacji ludzi chorych ogółem','italic'))
    print(black('Specifity - procentowy udział ludzi zdrowych uznanych za zdrowych w populacji ludzi zdrowych','italic'))
In [39]:
Type_error(classifiers_A,name, X_train, y_train,X_test,y_test)
+-----------------------+-------+-------+-------+-------+-------+-------+
|          Name         |  NBC  |  LRE  |  GBC  |  RFC  |  LGBM |  CBC  |
+-----------------------+-------+-------+-------+-------+-------+-------+
|  False_Positive_Rate: | 0.033 |  0.0  | 0.001 |  0.0  | 0.001 |  0.0  |
|  True_Positive_Rate:  | 0.217 | 0.006 | 0.006 | 0.006 | 0.019 | 0.013 |
| False_Negative_Rate:  | 0.783 | 0.994 | 0.994 | 0.994 | 0.981 | 0.987 |
|       Specifity       | 0.967 |  1.0  | 0.999 |  1.0  | 0.999 |  1.0  |
+-----------------------+-------+-------+-------+-------+-------+-------+
False_Positive_Rate - procentowy udział ludzi zdrowych uznanych przez model za chorych w populacji ludzi zdrowych
True_Positive_Rate RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem
False_Negative_Rate - procentowy udział niewykrytych chorych w populacji ludzi chorych ogółem
Specifity - procentowy udział ludzi zdrowych uznanych za zdrowych w populacji ludzi zdrowych

Jak widać w zbiorze wysoko niezbilansowanym modele uznawały prawie wszystkich za zdrowych. Ogolnie efektywność modeli w wykrywaniu ludzi chorych była bardzo słaba (True_Positive_Rate). W najlepszym modelu NBC wykrywalność ludzi chorych wynosiła około 20%, w pozostałych modelach wykrywalność była bliska zeru. Przy bardzo niezbilansowanych zbiorach, mimo że model nie wykrył żadnego chorego, zachował bardzo wysoki wskażnik accuracy.

In [40]:
Type_error(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
+-----------------------+--------+--------+--------+--------+---------+--------+
|          Name         | NBC_OV | LRE_OV | GBC_OV | RFC_OV | LGBM_OV | CBC_OV |
+-----------------------+--------+--------+--------+--------+---------+--------+
|  False_Positive_Rate: | 0.204  | 0.434  | 0.219  | 0.001  |  0.125  | 0.098  |
|  True_Positive_Rate:  | 0.764  | 0.771  | 0.758  | 0.025  |  0.529  | 0.465  |
| False_Negative_Rate:  | 0.236  | 0.229  | 0.242  | 0.975  |  0.471  | 0.535  |
|       Specifity       | 0.796  | 0.566  | 0.781  | 0.999  |  0.875  | 0.902  |
+-----------------------+--------+--------+--------+--------+---------+--------+
False_Positive_Rate - procentowy udział ludzi zdrowych uznanych przez model za chorych w populacji ludzi zdrowych
True_Positive_Rate RECALL - procentowy udział chorych dobrze zdiagnozowanych w populacji ludzi chorych ogółem
False_Negative_Rate - procentowy udział niewykrytych chorych w populacji ludzi chorych ogółem
Specifity - procentowy udział ludzi zdrowych uznanych za zdrowych w populacji ludzi zdrowych

Po zbilansowaniu zbiorów za pomocą oversampling okazało modele zaczęły znacznie lepiej wykrywać ludzi chorych w populacji ludzi chorych (True_Positive_Rate). Model NBC posiada teraz wykrywalność rzędu 76%, przy okazji około 20% ludzi zdrowych model ten uznaje za chorych (False_Positive_Rate). Model LogisticRegresion ma podobną wykrywalność ludzi chorych co NBC na poziomie 77% niestety ma znacznie wyższy 43 procentowy udział określania ludzi zdrowych jako chorych (False_Positive_Rate).

In [ ]:
 

Binary Class Plot

In [41]:
def BinaryClassPlot(six_classifiers,name, X_train, y_train,X_test,y_test):

    from plot_metric.functions import BinaryClassification

    for cls in six_classifiers:
        cls.fit(X_train, y_train) 
    
    plt.figure(figsize=(15,7))
    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for i in range(6):
        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col]) 
        ax.title.set_color('blue')
    
        model = six_classifiers[i]
        bc = BinaryClassification(y_test, model.predict_proba(X_test)[:,1], labels=["Class 1", "Class 2"])
        bc.plot_roc_curve(title=type(six_classifiers[i]).__name__)

Najbardziej na wykresie ROC AUC przyciąga uwagę czerwony punkt. Współczędne czerwonego punktu znajdują się na osi X i y. wspołczędne osi X to False Positive Rate.
Punkt ten ma wsplrzędne dla X:False_Positive_Rate, y:True_Positive_Rate.

In [42]:
BinaryClassPlot(classifiers_A,name, X_train, y_train,X_test,y_test)
In [43]:
BinaryClassPlot(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
In [44]:
def Precision_Recall_plot(six_classifiers,name, X_train, y_train,X_test,y_test):

    from sklearn.metrics import confusion_matrix, log_loss, auc, roc_curve, roc_auc_score, recall_score, precision_recall_curve

    for cls in six_classifiers:
        cls.fit(X_train, y_train) 
    
    fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(16,7))

    for cls, ax in zip(six_classifiers, axes.flatten()):
        precision, recall, thresholds = precision_recall_curve(y_test, cls.predict_proba(X_test)[:,1])
        ax.plot(recall, precision, marker='.', label='model')
        ax.title.set_text(type(cls).__name__)
        ax.title.set_color('blue')
        ax.set_xlabel('Recall', color='grey', alpha=0.8)
        ax.set_ylabel('Precision', color='grey', alpha=0.8)
 
    plt.tight_layout()
    plt.show()
In [45]:
Precision_Recall_plot(classifiers_A,name, X_train, y_train,X_test,y_test)
In [46]:
Precision_Recall_plot(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)

Cohen Kappa Metric


$ bbox[20px,border:1px solid red]
{
κ = displaystylefrac{(p_o – p_e)}{(1 – p_e)}=1-frac{1 – p_e}{1 – p_e}
qquad
} $

where:

$ p_0 = displaystylefrac{(tn+??)}{(tn+fp+fn+??)}$

$ p_{empire} = displaystylefrac{(tn+fp)}{(tn+fp+fn+??)}timesfrac{(tn+fn)}{(tn+fp+fn+??)}$

$ p_{theory} = displaystylefrac{(fn+??)}{(tn+fp+fn+??)}timesfrac{(fp+??)}{(tn+fp+fn+??)}$

$ p_e = p_{empire}+p_{theory}$

Zaleca się korzystać z kappa Cohena obliczając go osobno dla każdej etykiety. W przeciwnym razie najlepszym rozwiązaniem byłoby użycie Alfa Krippendorffa do określania niezawodności między oceniającymi



$begin{matrix} tn & fp \ fn & tp
end{matrix}$

tn = 45
fp = 15
fn = 25
tp = 15

p_0 = (tn+??)/(tn+fp+fn+??)
print(‘p_0:’,p_0)

P_yes = ((tn+fp)/(tn+fp+fn+??))*((tn+fn)/(tn+fp+fn+??))
print(‘P_yes: ‘,P_yes)

P_no = ((fn+??)/(tn+fp+fn+??))*((fp+??)/(tn+fp+fn+??))
print(‘P_no: ‘,P_no)

pe = P_yes + P_no
print(‘pe: ‘,pe)

κ = (p_0-pe)/(1-pe)
print(‘κ: ‘,κ)

In [47]:
def Cohen_Kappa(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    from sklearn import metrics
    import simple_colors
    
    for cls in six_classifiers:
        cls.fit(X_train, y_train)    
    
    
    κ = ['κ:']
    p0 = ['p0:']
    pe = ['pe:']
    
    def compute_metric(model):
        
        from sklearn.metrics import confusion_matrix

        model.fit(X_train,y_train)
        cm = confusion_matrix(y_test, model.predict(X_test))
        tn, fp, fn, tp = cm.ravel()     
        
        p0 = (tn+??)/(tn+fp+fn+??)
        P_empire = ((tn+fp)/(tn+fp+fn+??))*((tn+fn)/(tn+fp+fn+??))
        P_theory = ((fn+??)/(tn+fp+fn+??))*((fp+??)/(tn+fp+fn+??))
        pe = P_empire + P_theory
        κ = (p0-pe)/(1-pe)
        
        κ = np.round(κ,decimals=3)
        p0 = np.round(p0,decimals=3)
        pe = np.round(pe,decimals=3)
        
        return κ,p0, pe

    for cls in six_classifiers:
        
        results = compute_metric(cls)
        κ.append(blue(results[0],'bold'))
        p0.append(results[1])
        pe.append(results[2])
      

    t = PrettyTable(['Name', name[0],name[1],name[2],name[3],name[4],name[5]])
    t.add_row(p0)
    t.add_row(pe)
    t.add_row(κ)

    
    print(t)
    
    print(blue('Obserwowana zgodność p0', 'underlined'))
    print(black('Jest to prawdopodobieństwo dobrego wyboru, to procent przypadków, które zostały sklasyfikowane prawidłowo w całej matrycy zamieszania, czyli prawdziwi chorzy zostali sklasyfikowani jako chorzy a prawdziwie zdrowi sklasyfikowani jako prawdziwie zdrowi','italic'))
    print(blue('Oczekiwana zgodność pe', 'underlined'))
    print(black('Jest to prawdopodobieństwo wyboru bezpośrednio związana z liczbą wystąpień każdej klasy. Jeżeli wystąpień klas było po równo (np. 1: 20 wystąpień i 0: 20 wystapień), czyli zbiór był zbilansowany, to prawdopodobieństwo wynosi 50%. ','italic'))
    print(blue('Cohen Kappa mówi, o ile lepszy jest model klasyfikacji (p0) od losowego klasyfikatora(pe), który przewiduje na podstawie częstotliwości klas.','italic'))
    print(black(''))
    print(black('Statystyka może być ujemna, co oznacza, że nie ma skutecznej zgodności między dwoma wskaźnikami lub zgodność jest gorsza niż losowa.'))
In [48]:
Cohen_Kappa(classifiers_A,name, X_train, y_train,X_test,y_test)
+------+-------+-------+-------+-------+-------+-------+
| Name |  NBC  |  LRE  |  GBC  |  RFC  |  LGBM |  CBC  |
+------+-------+-------+-------+-------+-------+-------+
| p0:  | 0.954 | 0.982 | 0.981 | 0.982 | 0.981 | 0.982 |
| pe:  | 0.947 | 0.981 | 0.981 | 0.982 | 0.981 | 0.981 |
|  κ:  | 0.124 | 0.012 | 0.011 | 0.037 | 0.033 | 0.024 |
+------+-------+-------+-------+-------+-------+-------+
Obserwowana zgodność p0
Jest to prawdopodobieństwo dobrego wyboru, to procent przypadków, które zostały sklasyfikowane prawidłowo w całej matrycy zamieszania, czyli prawdziwi chorzy zostali sklasyfikowani jako chorzy a prawdziwie zdrowi sklasyfikowani jako prawdziwie zdrowi
Oczekiwana zgodność pe
Jest to prawdopodobieństwo wyboru bezpośrednio związana z liczbą wystąpień każdej klasy. Jeżeli wystąpień klas było po równo (np. 1: 20 wystąpień i 0: 20 wystapień), czyli zbiór był zbilansowany, to prawdopodobieństwo wynosi 50%. 
Cohen Kappa mówi, o ile lepszy jest model klasyfikacji (p0) od losowego klasyfikatora(pe), który przewiduje na podstawie częstotliwości klas.

Statystyka może być ujemna, co oznacza, że nie ma skutecznej zgodności między dwoma wskaźnikami lub zgodność jest gorsza niż losowa.
In [49]:
Cohen_Kappa(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
+------+--------+--------+--------+--------+---------+--------+
| Name | NBC_OV | LRE_OV | GBC_OV | RFC_OV | LGBM_OV | CBC_OV |
+------+--------+--------+--------+--------+---------+--------+
| p0:  | 0.795  | 0.569  | 0.781  | 0.982  |  0.869  | 0.894  |
| pe:  | 0.775  | 0.558  | 0.762  | 0.981  |  0.855  | 0.881  |
|  κ:  | 0.089  | 0.027  |  0.08  | 0.046  |  0.099  | 0.109  |
+------+--------+--------+--------+--------+---------+--------+
Obserwowana zgodność p0
Jest to prawdopodobieństwo dobrego wyboru, to procent przypadków, które zostały sklasyfikowane prawidłowo w całej matrycy zamieszania, czyli prawdziwi chorzy zostali sklasyfikowani jako chorzy a prawdziwie zdrowi sklasyfikowani jako prawdziwie zdrowi
Oczekiwana zgodność pe
Jest to prawdopodobieństwo wyboru bezpośrednio związana z liczbą wystąpień każdej klasy. Jeżeli wystąpień klas było po równo (np. 1: 20 wystąpień i 0: 20 wystapień), czyli zbiór był zbilansowany, to prawdopodobieństwo wynosi 50%. 
Cohen Kappa mówi, o ile lepszy jest model klasyfikacji (p0) od losowego klasyfikatora(pe), który przewiduje na podstawie częstotliwości klas.

Statystyka może być ujemna, co oznacza, że nie ma skutecznej zgodności między dwoma wskaźnikami lub zgodność jest gorsza niż losowa.

Zbilansowanie zbiorów w sposób nieznaczny wpłynęło na to, aby obserwowana zgodność p0 była lepsza od oczekiwanej zgodność pe.

Matthews Correlation Coefficient MCC

Współczynnik korelacji Matthewsa (MCC) ma zakres od -1 do 1, gdzie -1 oznacza całkowicie zły klasyfikator binarny, a 1 oznacza całkowicie poprawny klasyfikator binarny.


$ bbox[23px,border:1px solid red]
{
MCC = displaystylefrac{{(tp times tn)}-{(fp times fn)}}{(tp+fp)(tp+fn)(tn+fp)(tn+fn)}
qquad
} $

In [50]:
def MCC(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    from sklearn import metrics
    import simple_colors
    
    for cls in six_classifiers:
        cls.fit(X_train, y_train)    
    
    
    MCC = ['MCC:']
    
    def compute_metric(model):
        
        from sklearn.metrics import confusion_matrix

        model.fit(X_train,y_train)
        cm = confusion_matrix(y_test, model.predict(X_test))
        tn, fp, fn, tp = cm.ravel()     
        
        MCC = ((tp*tn)-(fp*fn))/(((tp+fp)*(tp+fn)*(tn+fp)*(tn+fn))** .5)
        MCC = np.round(MCC,decimals=6)
        MCC
        
        return MCC

    for cls in six_classifiers:
        
        results = compute_metric(cls)
        MCC.append(results)
             

    t = PrettyTable(['Name', name[0],name[1],name[2],name[3],name[4],name[5]])
    t.add_row(MCC)
    
    print('Matthews Correlation Coefficient MCC')
    print(t)
       
    print(black('Współczynnik korelacji Matthewsa (MCC) ma zakres od -1 do 1, gdzie -1 oznacza całkowicie zły klasyfikator binarny, a 1 oznacza całkowicie poprawny klasyfikator binarny','italic'))
    
In [51]:
MCC(classifiers_A,name, X_train, y_train,X_test,y_test)
Matthews Correlation Coefficient MCC
+------+----------+----------+----------+----------+----------+----------+
| Name |   NBC    |   LRE    |   GBC    |   RFC    |   LGBM   |   CBC    |
+------+----------+----------+----------+----------+----------+----------+
| MCC: | 0.131974 | 0.037365 | 0.037365 | 0.079088 | 0.068063 | 0.077645 |
+------+----------+----------+----------+----------+----------+----------+
Współczynnik korelacji Matthewsa (MCC) ma zakres od -1 do 1, gdzie -1 oznacza całkowicie zły klasyfikator binarny, a 1 oznacza całkowicie poprawny klasyfikator binarny
In [52]:
MCC(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
Matthews Correlation Coefficient MCC
+------+----------+----------+----------+----------+----------+---------+
| Name |  NBC_OV  |  LRE_OV  |  GBC_OV  |  RFC_OV  | LGBM_OV  |  CBC_OV |
+------+----------+----------+----------+----------+----------+---------+
| MCC: | 0.182012 | 0.090326 | 0.171176 | 0.109832 | 0.159023 | 0.15968 |
+------+----------+----------+----------+----------+----------+---------+
Współczynnik korelacji Matthewsa (MCC) ma zakres od -1 do 1, gdzie -1 oznacza całkowicie zły klasyfikator binarny, a 1 oznacza całkowicie poprawny klasyfikator binarny

Plot_roc

In [53]:
def plot_roc(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    import scikitplot as skplt
    
    plt.figure(figsize=(15,7))
    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for cls in classifiers_A:
        cls.fit(X_train, y_train)

    for i in range(6):

        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col]) 
        ax.title.set_color('blue')

        model = classifiers_A[i]
        skplt.metrics.plot_roc(y_test, model.predict_proba(X_test), ax=ax, title=type(six_classifiers[i]).__name__)

    plt.show()

W tym teście szczególnie waża jest różnica pomiedzy krzywą micro-average ROC pokazaną na różowo oraz krzywą macro-average ROC pokazana na granatowo.
Idealnie gdy obie krzywe się pokrywają. Zbilansowanie klas prze oversampling poprawiło w wielu medelach spójność obu krzywych, w niektórych jednak pozostały duże różnice.

Jeżeli:

macro average ROC > micro average ROC
wtedy mówimy, że: “1 (minority) is better classified than 0 (majority) – macro > micro”

Jeżeli:

macro average ROC micro average ROC
wtedy mówimy, że: ‘0 (majority) is better classified than 1 (minority)- micro < macro’

Idealnie gdy krzywe micro i macro pokrywają się ze sobą. Taka sytuacja ma miejsce po oversampling w GaussianNB oraz GradientBoostingClassifier.

In [54]:
plot_roc(classifiers_A,name, X_train, y_train,X_test,y_test)
In [55]:
plot_roc(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
In [56]:
def Trainsize(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    from mlxtend.plotting import plot_learning_curves
    
    for cls in six_classifiers:
        cls.fit(X_train, y_train) 
        
    plt.figure(figsize=(15,7))

    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for i in range(6):
        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col]) 
        ax.title.set_text(type(six_classifiers[i]).__name__)
        ax.title.set_color('blue')
    
        model = six_classifiers[i]
        plot_learning_curves(X_train, y_train, 
                             X_test, y_test, 
                             model, print_model=False, style='ggplot')

Learning_curves

Najważniejsza są informacje:

  • jeżeli zbiór testowy i treningowy bardzo odstają od siebie oznacza to przeuczenie modelu
  • znajduje się miejsce gdzie oba wykresy testowy i treningowy są najbliżej siebie. Dla takiej wielkości próby model działa najlepiej w kontekście przeuczenia
  • na wykresie należy brać pod uwagę wielkość błędu klasyfikacji (oś y)

Dobrym wskaźnikiem przeuczenia jest różnica predykcji pomiedzy zbiorem treningowym i zbiorem testowym.
W modelu GaussianNB przed oversampling występuje średni błąd wielkości 0.045. oba zbiory testowy i treningowy są blisko siebie dla każdej wielkości próby. Wpodobnie było w przypadku LogisticRegression, natomiast model GradientBoosting poprawiał swój poziom nauki wraz z wielkością próbki.

Najmniejsza różnica pomiędzy błedami zbioru treningowego i testowego dla modelu LGBMClassifier znajduje sie dla 90% zbioru treningowego.

In [57]:
Trainsize(classifiers_A,name, X_train, y_train,X_test,y_test)
In [58]:
Trainsize(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
In [59]:
y_train.dtypes
Out[59]:
dtype('int64')
In [60]:
def ks_statistic(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    import scikitplot as skplt
    
    plt.figure(figsize=(15,7))
    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for cls in six_classifiers:
        cls.fit(X_train, y_train)

    for i in range(6):

        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col]) 
        ax.title.set_color('blue')

        model = classifiers_A[i]
        # skplt.metrics.plot_roc(y_test, model.predict_proba(X_test), ax=ax, title=type(six_classifiers[i]).__name__)
        skplt.metrics.plot_ks_statistic(y_test, model.predict_proba(X_test), ax=ax,title=type(six_classifiers[i]).__name__)
        
    plt.show()
In [61]:
ks_statistic(classifiers_A,name, X_train, y_train,X_test,y_test)
In [62]:
ks_statistic(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
In [63]:
def precision_recall2(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    import scikitplot as skplt
    
    plt.figure(figsize=(15,7))
    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for cls in six_classifiers:
        cls.fit(X_train, y_train)

    for i in range(6):

        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col]) 
        ax.title.set_color('blue')

        model = six_classifiers[i]

        skplt.metrics.plot_precision_recall(y_test, model.predict_proba(X_test), ax=ax,title=type(six_classifiers[i]).__name__)
    plt.show()
    print(blue('Jest to krzywa łącząca precyzję (PPV) i Recall (TPR) na jednym wykresie. Im wyższa krzywa na osi y, tym lepsza wydajność modelu. Informuje, przy którym recall, precision zaczyna spadać, może pomóc wybrać próg'))
In [64]:
precision_recall2(classifiers_A,name, X_train, y_train,X_test,y_test)
Jest to krzywa łącząca precyzję (PPV) i Recall (TPR) na jednym wykresie. Im wyższa krzywa na osi y, tym lepsza wydajność modelu. Informuje, przy którym recall, precision zaczyna spadać, może pomóc wybrać próg
In [65]:
precision_recall2(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
Jest to krzywa łącząca precyzję (PPV) i Recall (TPR) na jednym wykresie. Im wyższa krzywa na osi y, tym lepsza wydajność modelu. Informuje, przy którym recall, precision zaczyna spadać, może pomóc wybrać próg

Jak widac na wykresach problemem jest precyzjia klasy 1. Nie pomogła w tym zbilansowanie zbiorów przez oversampling.

In [66]:
def calibration_curve2(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    import scikitplot as skplt
    from sklearn.calibration import CalibratedClassifierCV, calibration_curve
    
    plt.figure(figsize=(15,7))
    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for cls in six_classifiers:
        cls.fit(X_train, y_train)

    for i in range(6):

        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col])
        ax.title.set_color('blue')

       
        
        model = classifiers_A[i]
        A_probas = model.fit(X_train, y_train).predict_proba(X_test)
        probas_list = [A_probas]
        
        clf_names =  [name[i]]
        
        skplt.metrics.plot_calibration_curve(y_test,probas_list,clf_names,title=type(six_classifiers[i]).__name__,ax=ax)  
    
    plt.show()
In [67]:
calibration_curve2(classifiers_A,name, X_train, y_train,X_test,y_test)
In [68]:
calibration_curve2(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)

Wyraźnie widać, że modele nie są skalibrowane.
https://machinelearningmastery.com/calibrated-classification-model-in-scikit-learn/

Co by sie stałom gdybyśmy je teraz spróbowali skalibrować?

In [70]:
from sklearn.datasets import make_classification
from sklearn.calibration import CalibratedClassifierCV
from sklearn.calibration import calibration_curve

import scikitplot as skplt
from sklearn.calibration import CalibratedClassifierCV, calibration_curve


NBC = GaussianNB() 
LRE = LogisticRegression(solver='lbfgs')
GBC = GradientBoostingClassifier()
RFC = RandomForestClassifier()
LGBM = LGBMClassifier() 
CBC = CatBoostClassifier(verbose=0, n_estimators=100)


classifiers_A = [NBC,LRE,GBC,RFC,LGBM,CBC]
name = ['NBC','LRE','GBC','RFC','LGBM','CBC']

#for cls in classifiers_A:
#    calibrated = CalibratedClassifierCV(cls, method='sigmoid', cv=5)
#    calibrated.fit(X_train, y_train)

plt.figure(figsize=(15,7))
grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)    
    

for i in range(6):

    col, row = i%3,i//3
    ax = plt.subplot(grid[row,col])
    ax.title.set_color('blue')

    model = classifiers_A[i]
    calibrated = CalibratedClassifierCV(model, method='sigmoid', cv=5)
    calibrated.fit(X_train, y_train)
    A_probas = calibrated.fit(X_train, y_train).predict_proba(X_test)
    probas_list = [A_probas]
        
    clf_names =  [name[i]]
        
    skplt.metrics.plot_calibration_curve(y_test,probas_list,clf_names,title=type(classifiers_A[i]).__name__,ax=ax)  
    
plt.show()
In [72]:
def cumulative_gain(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    plt.figure(figsize=(15,7))
    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for cls in six_classifiers:
        cls.fit(X_train, y_train)

    for i in range(6):

        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col]) 
        ax.title.set_color('blue')

        model = six_classifiers[i]

        skplt.metrics.plot_cumulative_gain(y_test, model.predict_proba(X_test), ax=ax,title=type(six_classifiers[i]).__name__)
        
    plt.show()
In [73]:
cumulative_gain(classifiers_A,name, X_train, y_train,X_test,y_test)
In [74]:
cumulative_gain(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)
In [76]:
def lift_curve(six_classifiers,name, X_train, y_train,X_test,y_test):
    
    plt.figure(figsize=(15,7))
    grid = plt.GridSpec(2, 3, wspace=0.3, hspace=0.4)

    for cls in six_classifiers:
        cls.fit(X_train, y_train)

    for i in range(6):

        col, row = i%3,i//3
        ax = plt.subplot(grid[row,col]) 
        ax.title.set_color('blue')

        model = six_classifiers[i]

        skplt.metrics.plot_lift_curve(y_test, model.predict_proba(X_test), ax=ax,title=type(six_classifiers[i]).__name__)
        
    plt.show()
In [77]:
lift_curve(classifiers_A,name, X_train, y_train,X_test,y_test)
In [78]:
lift_curve(classifiers_OV,name2, Xtrain_OV, ytrain_OV,X_test,y_test)

obraz.png