Projeto da disciplina de Data Mining

PESC - Programa de Engenharia de Sistemas e Computação

COPPE / UFRJ

Autores: Bernardo Souza e Rafael Lopes Conde dos Reis

GitHub: https://github.com/condereis/credit-card-default/

Resumo

Pacotes Utilizados


In [5]:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

from scipy.stats import randint, uniform

from sklearn.decomposition import PCA
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score, train_test_split, GridSearchCV, RandomizedSearchCV
from sklearn.metrics import auc, confusion_matrix, roc_curve
from sklearn.naive_bayes import GaussianNB
from sklearn.neural_network import MLPClassifier
from sklearn.svm import SVC

import tensorflow as tf
import tflearn
from tflearn.data_utils import to_categorical

%matplotlib inline

In [2]:
train = pd.read_csv('../data/processed/train.csv', index_col=0)
del train.index.name

X = train.drop('default.payment.next.month', axis=1)
y = train['default.payment.next.month']
train.head()


Out[2]:
LIMIT_BAL SEX MARRIAGE AGE PAY_0 PAY_2 PAY_3 PAY_4 PAY_5 PAY_6 ... PAY_AMT3 PAY_AMT4 PAY_AMT5 PAY_AMT6 default.payment.next.month RELATIVE_DEBT ED_1 ED_2 ED_3 ED_4
4937 -1.136720 0 0 -1.246020 -1 -1 -1 -1 -1 -2 ... -0.252500 -0.308063 -0.314136 -0.293382 0 0.941268 1 0 0 0
4789 -0.365981 0 1 1.791598 2 0 0 0 0 0 ... -0.126411 0.011102 -0.117776 -0.124626 0 -0.692300 0 1 0 0
8448 -0.751350 1 0 -1.029047 0 0 0 0 0 0 ... -0.068138 -0.259869 -0.246785 0.004922 1 0.017958 0 1 0 0
4536 0.481833 0 0 0.164303 1 -2 -2 -2 -2 -2 ... -0.296801 -0.308063 -0.314136 -0.293382 1 0.941268 0 1 0 0
27564 -0.288907 0 0 2.225543 0 0 0 0 0 0 ... 0.157572 -0.052731 -0.052323 -0.012122 0 -1.362466 0 1 0 0

5 rows × 28 columns

Benchmarks

Escolha da classe mais frequente

A acurácia de escolher a classe mais frequente é de 77,92%. Isso mostra que esse é um problema de classes desbalanceadas, de forma que a acurácia não é a melhor métrica para ser utilizada. Escolheu-se usar, então, como métrica a AUC (area under the curve), que representa a área sobre a curva ROC. A curva ROC, nada mais é do que uma curva, onde cada ponto corresponde a probabilidade de detecção e a probabilidade de falso alarme para um determinado valor de limiar de classificação.


In [74]:
print 'Acurácia:', max(y.mean(), 1-y.mean())
sns.countplot(y)


Acurácia: 0.779238095238
Out[74]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f53dcb9e8d0>

Naive Bayes

Foi utilizado como banchmarck para os demais modelos testados a eficiência em termos de AUC do Naive Bayes. Como pode-se observar, a eficiência A seguir é plotada a curva ROC e a matriz de confusão para o limiar de corte em 0.5.


In [75]:
model = GaussianNB()
scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
print 'Naive Bayes (AUC): %f +- %f' % (np.mean(scores), np.std(scores))
scores = cross_val_score(model, X, y, cv=5, n_jobs=-1)
print 'Naive Bayes (acurácia): %f +- %f' % (np.mean(scores), np.std(scores))


Naive Bayes (AUC): 0.739978 +- 0.007739
Naive Bayes (acurácia): 0.603518 +- 0.039904

In [5]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
model = GaussianNB()
y_pred = model.fit(X_train,y_train).predict(X_test)
labels = ['Pago', 'Default']
mtx = confusion_matrix(y_test, y_pred)
mtx = [x/float(sum(x)) for x in mtx]
sns.heatmap(pd.DataFrame(mtx, columns=labels, index=labels), annot=True, fmt=".2f", linewidths=.5)
plt.xlabel('Valor Classificado')
plt.ylabel('Valor Real')


Out[5]:
<matplotlib.text.Text at 0x7f53df467b50>

In [6]:
y_pred = model.fit(X_train, y_train).predict_proba(X_test)
y_pred = np.array([x[1] for x in y_pred])
fpr, tpr, thresholds = roc_curve(y_test+1, y_pred, pos_label=2)
roc_auc = auc(fpr, tpr)
plt.figure()
lw = 2
plt.plot(fpr, tpr, color='darkorange',
         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()


K-Nearest Neighbors

Redução de dimensionalidade

A redução de dimensionalidade apresentou dois picos, um em 6 e outro em 10 dimensões. Optouse por utilizar 10 dimensões, que apresentou um valor ligeiramente superior em termos de AUC.


In [6]:
score_list = []
error_list = []
n_dims = range(1,train.shape[1])
model = KNeighborsClassifier()
for dim in n_dims:
    reduced_data = PCA(n_components=dim).fit_transform(X)
    scores = cross_val_score(model, reduced_data, y,
                             cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(n_dims, score_list, yerr=error_list)


Out[6]:
<Container object of 3 artists>

In [10]:
X_red = CA(n_components=10).fit_transform(X)

Optou-se por utilizar um numero de vizinhos em torno do joelho da curva, que ocorreu em torno de 50. Com uma variação mais fina, chegou-se ao valor de 62 vizinhos.


In [16]:
k_list = [1, 10, 10, 50, 100, 150, 200]
score_list = []
error_list = []
for k in k_list:
    model = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(model, X_red, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(k_list, score_list, yerr=error_list)


Out[16]:
<Container object of 3 artists>

In [20]:
k_list = range(45,70,2)
score_list = []
error_list = []
for k in k_list:
    model = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(model, X_red, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(k_list, score_list, yerr=error_list)


Out[20]:
<Container object of 3 artists>

In [21]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
model = KNeighborsClassifier(n_neighbors=62)
y_pred = model.fit(X_train, y_train).predict_proba(X_test)
y_pred = np.array([x[1] for x in y_pred])
fpr, tpr, thresholds = roc_curve(y_test+1, y_pred, pos_label=2)
roc_auc = auc(fpr, tpr)
plt.figure()
lw = 2
plt.plot(fpr, tpr, color='darkorange',
         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()


Logistic Regression

Redução de dimensionalidade

Redução de dimensionalidade por PCA não mostrou nenhuma melhora significativa nos resultados. Optou-se, portanto, por utilizar os dados sem redução.


In [27]:
score_list = []
error_list = []
n_dims = range(1,train.shape[1])
model = LogisticRegression()
for dim in n_dims:
    reduced_data = PCA(n_components=dim).fit_transform(X)
    scores = cross_val_score(model, reduced_data, y,
                             cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(n_dims, score_list, yerr=error_list)


Out[27]:
<Container object of 3 artists>

In [22]:
O paramentros de regularização C que escolheu-se utilizar foi C = 1 pois ser um dos valores que resultaram no maior AUC e por estar próximo do joelho da curva.m


  File "<ipython-input-22-d482ba191ecd>", line 1
    O paramentros de regularização C que escolheu-se utilizar foi C = 1 pois ser um dos valores que resultaram no maior AUC e por estar próximo do joelho da curva.m
                ^
SyntaxError: invalid syntax

In [36]:
c_list = [0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000, 10000]
score_list = []
error_list = []
for c in c_list:
    model = LogisticRegression(C=c)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(c_list, score_list, yerr=error_list)
plt.xscale('log')



In [40]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
model = LogisticRegression(C=0.1)
y_pred = model.fit(X_train, y_train).predict_proba(X_test)
y_pred = np.array([x[1] for x in y_pred])
fpr, tpr, thresholds = roc_curve(y_test+1, y_pred, pos_label=2)
roc_auc = auc(fpr, tpr)
plt.figure()
lw = 2
plt.plot(fpr, tpr, color='darkorange',
         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()


Gradient Boosting Trees

Redução de dimensionalidade

Pode-se observar que a redução do número de dimensões com PCA não gerou uma melhora do AUC. Entretretanto o score de AUC para 15 dimensões é bem próximo do valor máximo encontrado e poderia ser usado para gerar um modelo mais leve computacionalmente.


In [15]:
score_list = []
error_list = []
n_dims = range(1,train.shape[1])
model = GradientBoostingClassifier()
for dim in n_dims:
    reduced_data = PCA(n_components=dim).fit_transform(X)
    scores = cross_val_score(model, reduced_data, y,
                             cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(n_dims, score_list, yerr=error_list)


Out[15]:
<Container object of 3 artists>

Taxa de Aprendizado

A variação da taxa de aprendizado mostrou um máximo do score de AUC em uma tak de aprendizado de aproximadamente 0,1.


In [52]:
score_list = []
error_list = []
learning_rates = np.logspace(-4,1,20)
for learning_rate in learning_rates:
    model = GradientBoostingClassifier(learning_rate=learning_rate)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(learning_rates, score_list, yerr=error_list)
plt.xscale('log')


Subamostragem

Corresponde a porcentagem de amostras que são utilizadas para fitar os classificadores do ensemble. Valores menores que 1 tendem a reduzir a variância porém aumentar o bias do modelo. Não se observou melhora significativa ao varia a taxa de subamostragem, portanto optou-se por mante-la igual a 1.


In [62]:
score_list = []
error_list = []
subsamples = np.linspace(0.1,1,10)
for subsample in subsamples:
    model = GradientBoostingClassifier(subsample=subsample)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(subsamples, score_list, yerr=error_list)


Out[62]:
<Container object of 3 artists>

Numero de features a se considerar para um split

Utilizar um número de features menor que o total costuma reduzir a variância do modelo em troca de um aumento do bias. Ao variar essa taxa não observou-se um valor estatisticamente superior ao outro, devido as grandes barras de erro. Optou-se por utilizar a taxa que apresentou maior AUC médio, no caso, 0.2.


In [63]:
score_list = []
error_list = []
max_features_list = np.linspace(0.1,1,10)
for max_features in max_features_list:
    model = GradientBoostingClassifier(max_features=max_features)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(max_features_list, score_list, yerr=error_list)


Out[63]:
<Container object of 3 artists>

Máxima Profundidade

Variou-se o número de camadas máximo que cada árvore pode ter. Não observou-se um valor estatisticamente superior ao outro, devido as grandes barras de erro. Optou-se por utilizar a taxa que apresentou maior AUC médio, no caso, 3.


In [68]:
score_list = []
error_list = []
max_depths = [1,2,3,4,5,6]
for max_depth in max_depths:
    model = GradientBoostingClassifier(max_features=0.2, max_depth=max_depth)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(max_depths, score_list, yerr=error_list)


Out[68]:
<Container object of 3 artists>

Mínimo de amostras para um split


In [71]:
score_list = []
error_list = []
min_samples_splits = np.logspace(-5,0,10)
for min_samples_split in min_samples_splits:
    model = GradientBoostingClassifier(max_features=0.2, min_samples_split=min_samples_split)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(min_samples_splits, score_list, yerr=error_list)
plt.xscale('log')



In [72]:
score_list = []
error_list = []
min_samples_splits = [2,3,4,5,6,7,8,9,10]
for min_samples_split in min_samples_splits:
    model = GradientBoostingClassifier(max_features=0.2, min_samples_split=min_samples_split)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(min_samples_splits, score_list, yerr=error_list)


Out[72]:
<Container object of 3 artists>

Mínimo de amostras em uma folha


In [83]:
score_list = []
error_list = []
min_samples_leafs = [1,10,20,30,40,50,60,70]
for min_samples_leaf in min_samples_leafs:
    model = GradientBoostingClassifier(max_features=0.2, min_samples_leaf=min_samples_leaf)
    scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
    score_list.append(np.mean(scores))
    error_list.append(np.std(scores))
plt.errorbar(min_samples_leafs, score_list, yerr=error_list)


Out[83]:
<Container object of 3 artists>

Busca Aleatórea de Parâmetros

A busca de parâmetros até agora considerou os parâmetros como sendo independentes. Buscar a melhor combinação de todos os parâmetros testados seria computacionalmente inviável. Optou-se por comparar o melhor modelo obtido com a busca de parametros supondo independencia com um modelo obtido através de uma busca por combinações aleatóreas de parâmentros.


In [108]:
param_dist = {"max_depth": randint(1, 11),
              "max_features": randint(1, 11),
              "min_samples_split": randint(2, 11),
              "min_samples_leaf": randint(1, 11),
              "subsample": uniform(0.1, 0.9),
              "learning_rate": uniform(0.001, 0.9),
              "loss" : ['deviance', 'exponential']}

# run randomized search
model = GradientBoostingClassifier()
random_search = RandomizedSearchCV(model, param_distributions=param_dist,n_iter=50)
random_search.fit(X, y)


/home/rafael/virtualenvs/credit-card-fraud/local/lib/python2.7/site-packages/sklearn/ensemble/gradient_boosting.py:490: RuntimeWarning: invalid value encountered in subtract
  np.sum(sample_weight * ((y * pred) - np.logaddexp(0.0, pred))))
Out[108]:
RandomizedSearchCV(cv=None, error_score='raise',
          estimator=GradientBoostingClassifier(criterion='friedman_mse', init=None,
              learning_rate=0.1, loss='deviance', max_depth=3,
              max_features=None, max_leaf_nodes=None,
              min_impurity_split=1e-07, min_samples_leaf=1,
              min_samples_split=2, min_weight_fraction_leaf=0.0,
              n_estimators=100, presort='auto', random_state=None,
              subsample=1.0, verbose=0, warm_start=False),
          fit_params={}, iid=True, n_iter=50, n_jobs=1,
          param_distributions={'loss': ['deviance', 'exponential'], 'subsample': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f53dc75b390>, 'learning_rate': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f53dcaf33d0>, 'min_samples_leaf': <scipy.stats._distn_infrastructure.rv_frozen obj...x7f53dc49a7d0>, 'max_depth': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f53dc49a290>},
          pre_dispatch='2*n_jobs', random_state=None, refit=True,
          return_train_score=True, scoring=None, verbose=0)

In [133]:
model = GradientBoostingClassifier(max_features=0.2)
scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
print 'Busca Independente: %f +- %f' % (np.mean(scores), np.std(scores))
model = random_search.best_estimator_
scores = cross_val_score(model, X, y, cv=5, n_jobs=-1, scoring='roc_auc')
print 'Busca Aleatórea: %f +- %f' % (np.mean(scores), np.std(scores))


Busca Independente: 0.782608 +- 0.010327
Busca Aleatórea: 0.778724 +- 0.010151

In [101]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
model = random_search.best_estimator_
y_pred = model.fit(X_train, y_train).predict_proba(X_test)
y_pred = np.array([x[1] for x in y_pred])
fpr, tpr, thresholds = roc_curve(y_test+1, y_pred, pos_label=2)
roc_auc = auc(fpr, tpr)
plt.figure()
lw = 2
plt.plot(fpr, tpr, color='darkorange',
         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()



In [ ]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
y_pred = model.fit(X_train,y_train).predict(X_test)
labels = ['Pago', 'Default']
mtx = confusion_matrix(y_test, y_pred)
mtx = [x/float(sum(x)) for x in mtx]
sns.heatmap(pd.DataFrame(mtx, columns=labels, index=labels), annot=True, fmt=".2f", linewidths=.5)
plt.xlabel('Valor Classificado')
plt.ylabel('Valor Real')

Rede Neural


In [62]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
with tf.Graph().as_default():
    net = tflearn.input_data(shape=[None, X.shape[1]])
    net = tflearn.fully_connected(net, 100, activation='relu')
    net = tflearn.dropout(net, 0.5)
    net = tflearn.fully_connected(net, 100, activation='relu')
    net = tflearn.dropout(net, 0.5)
    net = tflearn.fully_connected(net, 2, activation='softmax')
    net = tflearn.regression(net, optimizer='adam', loss='categorical_crossentropy')
    
    Y = np.array([[float(a), float(1-a)] for a in y_train])
    lm = tflearn.DNN(net)
    lm.fit(X_train.as_matrix(), Y, validation_set=0.3, show_metric=True, batch_size=200, n_epoch=50, snapshot_epoch=False)


Training Step: 2250  | total loss: 0.42779
| Adam | epoch: 050 | loss: 0.42779 - acc: 0.8224 -- iter: 8820/8820

In [61]:
y_pred = lm.predict(X_test)
y_pred = [a[0] for a in y_pred]
fpr, tpr, thresholds = roc_curve(y_test+1, y_pred, pos_label=2)
roc_auc = auc(fpr, tpr)
plt.figure()
lw = 2
plt.plot(fpr, tpr, color='darkorange',
         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title ('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()



In [ ]: