from datetime import datetime
print(f'Päivitetty {datetime.now()}')
Päivitetty 2022-09-05 19:02:03.714843
Data löytyy lähteestä: https://www.kaggle.com/mlg-ulb/creditcardfraud
Datassa ei ole alkuperäisiä selittäviä muuttujia, vaan niistä pääkomponenttianalyysilla muodostetut uudet muuttujat (sekä tietosuojan että tilastotieteellisten syiden takia). Class-muuttuja on kohdemuuttuja (0 = 'ei petos', 1 = 'petos').
Petoksia on vähän suhteessa kaikkiin luottokorttitapahtumiin. Tämän vuoksi data on syytä tasapainottaa. Käytän tasapainottamiseen imbalanced-learn-kirjastoa, joka ei ole Anacondassa valmiiksi asennettuna. Sen voi asentaa komentoriviltä (Anaconda prompt) komennolla:
conda install -c conda-forge imbalanced-learn
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# train_test_split osaa jakaa datan opetusdataan ja testidataan
from sklearn.model_selection import train_test_split
# Datan tasapainottamiseen
from imblearn.over_sampling import RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
# Kokeiltavat mallit
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
# Sekaannusmatriisin näyttämiseen
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
# Data on sen verran iso, että suosittelen sen lataamista
# omalle koneelle ennen avaamista!
df = pd.read_csv('https://taanila.fi/creditcard.csv')
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 284807 entries, 0 to 284806 Data columns (total 31 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Time 284807 non-null float64 1 V1 284807 non-null float64 2 V2 284807 non-null float64 3 V3 284807 non-null float64 4 V4 284807 non-null float64 5 V5 284807 non-null float64 6 V6 284807 non-null float64 7 V7 284807 non-null float64 8 V8 284807 non-null float64 9 V9 284807 non-null float64 10 V10 284807 non-null float64 11 V11 284807 non-null float64 12 V12 284807 non-null float64 13 V13 284807 non-null float64 14 V14 284807 non-null float64 15 V15 284807 non-null float64 16 V16 284807 non-null float64 17 V17 284807 non-null float64 18 V18 284807 non-null float64 19 V19 284807 non-null float64 20 V20 284807 non-null float64 21 V21 284807 non-null float64 22 V22 284807 non-null float64 23 V23 284807 non-null float64 24 V24 284807 non-null float64 25 V25 284807 non-null float64 26 V26 284807 non-null float64 27 V27 284807 non-null float64 28 V28 284807 non-null float64 29 Amount 284807 non-null float64 30 Class 284807 non-null int64 dtypes: float64(30), int64(1) memory usage: 67.4 MB
# Kohdemuuttujan jakauma
df['Class'].value_counts()
0 284315 1 492 Name: Class, dtype: int64
Kohdemuuttujan jakauma on epätasapainoinen.
# Selittävät muuttujat
X = df.drop('Class', axis=1)
# Kohdemuuttuja
y = df['Class']
# Jako opetus- ja testidataan
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=2)
Data on iso, jos tasapainotetaan kasvattamalla pienempää ryhmää isomman kokoiseksi (RandomOverSampler). Tämän seurauksena mallin sovittaminen kestää niin kauan että välillä ehtii kahville.
Nopeampi ratkaisu on tasapainottaa pienentämällä isompaa ryhmää (RandomUnderSampler), mutta tällä ei päästä yhtä hyviin malleihin.
# Tasapainotus ja mallien sovitus
# RandomOverSampler kasvattaa satunnaisotoksilla pienempää ryhmää
# Voit myös kokeilla RandomUnderSampler, joka toimii nopeammin
rs = RandomOverSampler(random_state=2)
X_train, y_train = rs.fit_resample(X_train, y_train)
lrc = LogisticRegression()
lrc.fit(X_train, y_train)
dtc = DecisionTreeClassifier(max_depth=3, random_state=2)
dtc.fit(X_train, y_train)
rfc = RandomForestClassifier(max_depth=4, random_state=2)
rfc.fit(X_train, y_train)
gbc = GradientBoostingClassifier(max_depth=2, random_state=2)
gbc.fit(X_train, y_train)
GradientBoostingClassifier(max_depth=2, random_state=2)
# Oikeaan osuneiden ennusteiden osuus opetusdatassa
print('Ennusteiden tarkkuus opetusdatassa:')
print(f'Logistinen regressio {lrc.score(X_train, y_train):.3f}')
print(f'Päätöspuu {dtc.score(X_train, y_train):.3f}')
print(f'Satunnaismetsä {rfc.score(X_train, y_train):.3f}')
print(f'Gradienttitehostus {gbc.score(X_train, y_train):.3f}')
Ennusteiden tarkkuus opetusdatassa: Logistinen regressio 0.937 Päätöspuu 0.943 Satunnaismetsä 0.947 Gradienttitehostus 0.973
# Oikeaan osuneiden ennusteiden osuus testidatassa
print(f'Logistinen regressio {lrc.score(X_test, y_test):.3f}')
print(f'Päätöspuu {dtc.score(X_test, y_test):.3f}')
print(f'Satunnaismetsä {rfc.score(X_test, y_test):.3f}')
print(f'Gradienttitehostus {gbc.score(X_test, y_test):.3f}')
Logistinen regressio 0.964 Päätöspuu 0.960 Satunnaismetsä 0.993 Gradienttitehostus 0.984
# Mallien antamat ennusteet testidatalle
y_test_lrc = lrc.predict(X_test)
y_test_dtc = dtc.predict(X_test)
y_test_rfc = rfc.predict(X_test)
y_test_gbc = gbc.predict(X_test)
cm = confusion_matrix(y_test, y_test_lrc)
ConfusionMatrixDisplay(confusion_matrix=cm).plot()
<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x1bfd4989c70>
Logistinen regressio tunnistaa parhaiten petoksia, mutta ennustaa paljon ok-tapahtumia petoksiksi.
cm = confusion_matrix(y_test, y_test_dtc)
ConfusionMatrixDisplay(confusion_matrix=cm).plot()
<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x1bfd5018f40>
cm = confusion_matrix(y_test, y_test_rfc)
ConfusionMatrixDisplay(confusion_matrix=cm).plot()
<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x1bfd74bdd60>
Satunnaismetsä erehtyy vähiten ok-tapahtumissa, mutta tunnistaa huonoiten petoksia.
cm = confusion_matrix(y_test, y_test_gbc)
ConfusionMatrixDisplay(confusion_matrix=cm).plot()
<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x1bfd755f9a0>
Gradienttitehostus on hyvä kompromissi väärien negatiivisten ja väärien positiivisten väliltä.
# Päätöspuumallin voin havainnollistaa kaaviona
plt.figure(figsize=(10, 6))
plot_tree(decision_tree=dtc, feature_names=X.columns)
[Text(0.4230769230769231, 0.875, 'V14 <= -1.808\ngini = 0.5\nsamples = 426440\nvalue = [213220, 213220]'), Text(0.15384615384615385, 0.625, 'V4 <= -0.476\ngini = 0.063\nsamples = 192739\nvalue = [6229, 186510]'), Text(0.07692307692307693, 0.375, 'gini = 0.0\nsamples = 2015\nvalue = [2015, 0]'), Text(0.23076923076923078, 0.375, 'V1 <= 1.992\ngini = 0.043\nsamples = 190724\nvalue = [4214, 186510]'), Text(0.15384615384615385, 0.125, 'gini = 0.035\nsamples = 189908\nvalue = [3398, 186510]'), Text(0.3076923076923077, 0.125, 'gini = 0.0\nsamples = 816\nvalue = [816, 0]'), Text(0.6923076923076923, 0.625, 'V4 <= 1.661\ngini = 0.202\nsamples = 233701\nvalue = [206991, 26710]'), Text(0.5384615384615384, 0.375, 'V20 <= -1.028\ngini = 0.122\nsamples = 203860\nvalue = [190556, 13304]'), Text(0.46153846153846156, 0.125, 'gini = 0.5\nsamples = 5630\nvalue = [2837, 2793]'), Text(0.6153846153846154, 0.125, 'gini = 0.1\nsamples = 198230\nvalue = [187719, 10511]'), Text(0.8461538461538461, 0.375, 'V12 <= -0.46\ngini = 0.495\nsamples = 29841\nvalue = [16435, 13406]'), Text(0.7692307692307693, 0.125, 'gini = 0.433\nsamples = 15590\nvalue = [4936, 10654]'), Text(0.9230769230769231, 0.125, 'gini = 0.312\nsamples = 14251\nvalue = [11499, 2752]')]