Transformando Dados em Segurança: Detecção de Fraudes em Pagamentos

Luciano Magalhães Luciano Magalhães   |   schedule Junho, 2024  |   view_object_track Machine Learning

1. Descrição Geral do Problema¶

Imagem de Detecção de Fraude

A fraude em transações financeiras representa um desafio significativo para instituições financeiras ao redor do mundo, causando prejuízos substanciais tanto para as instituições quanto para os clientes. O aumento das transações online e a sofisticação das técnicas fraudulentas tornam a detecção de fraudes uma tarefa cada vez mais complexa e crítica.

Neste contexto, o objetivo deste projeto é desenvolver um modelo preditivo capaz de identificar transações fraudulentas com alta precisão. A detecção eficaz de fraudes não só ajuda a minimizar as perdas financeiras, mas também a aumentar a segurança e a confiança dos clientes nos sistemas financeiros.

Data Science Workflow Canvas

Elemento Descrição
Problema de Negócio A crescente incidência de fraudes financeiras online está causando perdas significativas para instituições financeiras e clientes. O projeto pretende identificar transações fraudulentas com alta precisão para minimizar essas perdas e aumentar a segurança financeira.
Outcomes/Prediction Identificar transações fraudulentas com alta precisão utilizando técnicas de aprendizado de máquina.
Data Acquisition Conjunto de dados do Kaggle contendo informações sobre transações fraudulentas e não fraudulentas.
Data Preparation Transformação de variáveis categóricas em numéricas, normalização e redução de dimensionalidade.
Exploração de Dados Análise exploratória inicial para entender as características e distribuição das variáveis.
Modeling Treinamento de modelo Gradient Boosting escolhido e ajustado para prever transações fraudulentas, ajuste de hiperparâmetros e uso de GridSearchCV para otimizar a performance do modelo.
Model Evaluation Avaliação da performance do modelo usando precisão, recall, f1-score e matriz de confusão.
Prediction Teste do modelo com transações de exemplo ajustadas para verificar a capacidade de detecção de fraudes.
Conclusões e Recomendações Resultados obtidos e recomendações baseadas nas análises e modelagem.

2. Carregar Bibliotecas de Interesse¶


In [1]:
import pandas as pd  # Biblioteca para manipulação e análise de dados
import numpy as np   # Biblioteca para cálculos numéricos e operações com arrays

# Bibliotecas para visualização de dados
import matplotlib.pyplot as plt  # Biblioteca para criação de gráficos
import seaborn as sns            # Biblioteca para visualização de dados baseada no matplotlib

from sklearn.model_selection import GridSearchCV         # Ferramenta para otimização de hiperparâmetros com validação cruzada

from IPython.display import display, Markdown, HTML      # Ferramentas para exibição de dados no Jupyter Notebook

display(HTML("<style>.container { width:100% !important; }</style>"))

# Importação de modelos e métricas do scikit-learn
from sklearn.ensemble import GradientBoostingClassifier  # Modelo de classificação baseado em gradient boosting
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix  # Ferramentas para avaliação de modelos

from imblearn.over_sampling import SMOTE  # Técnica para balanceamento de classes através de superamostragem

# Função para dividir os dados em conjuntos de treinamento e teste
from sklearn.model_selection import train_test_split     # Função para divisão de dados em treinamento e teste

# Ferramentas de pré-processamento de dados
from sklearn.preprocessing import StandardScaler         # Ferramenta para normalização de dados

# Ferramenta para redução de dimensionalidade
from sklearn.decomposition import PCA                    # Análise de Componentes Principais para redução de dimensionalidade

import matplotlib.ticker as ticker  # Import necessário para usar FuncFormatter

import warnings
from IPython.display import display, HTML

# Suprimir todos os avisos de forma global
warnings.filterwarnings("ignore")

display(HTML("<script>console.warn = function() {}; console.error = function() {};</script>"))

3. Função de Uso em Gráficos¶

In [2]:
# Função para aplicar estilo aos gráficos

def estilo_grafico():
    
    sns.set_style('whitegrid')
    
    plt.rcParams.update({
        
        'axes.spines.top': False,         # Remover a borda superior
        
        'axes.spines.right': False,       # Remover a borda direita
        
        'axes.spines.left': False,        # Remover a borda esquerda
        
        'axes.spines.bottom': True,       # Manter a borda inferior
        
        'axes.edgecolor': 'black',        # Cor da borda dos eixos
        
        'axes.facecolor': 'white',        # Cor de fundo dos eixos
        
        'grid.color': 'gray',             # Cor da grade
        
        'grid.linestyle': '--',           # Estilo da linha da grade
        
        'grid.alpha': 0.7,                # Transparência da grade
        
        'grid.linewidth': 0.7,            # Largura da linha da grade
        
        'figure.facecolor': 'white',      # Cor de fundo da figura
        
        'savefig.facecolor': 'white',     # Cor de fundo ao salvar a figura
 
    })
    
    # Define a paleta de cores
    cores_personalizadas = ['#184E77', '#1e6091', '#1a759f', '#168aad', '#34a0a4', '#52b69a', '#76c893', '#99d98c', '#b5e48c', '#d9ed92']
    sns.set_palette(cores_personalizadas)

3.1 Função para Formatar Valor¶

In [3]:
# Função para formatar os valores em moeda americana sem símbolo
def formatar_valor(valor):
    return f'{valor:,.2f}'


4. Exploração Inicial dos Dados¶

4.1. Carrega os Dados e Exibi os Primeiros Registros¶

In [4]:
#********** carregar conjunto de dados ************
dados = pd.read_csv(r'data\data_fraud.csv')

#********* apresenta primeiros registros ***********
dados.head()
Out[4]:
step type amount nameOrig oldbalanceOrg newbalanceOrig nameDest oldbalanceDest newbalanceDest isFraud isFlaggedFraud
0 1 PAYMENT 9839.64 C1231006815 170136.0 160296.36 M1979787155 0.0 0.0 0 0
1 1 PAYMENT 1864.28 C1666544295 21249.0 19384.72 M2044282225 0.0 0.0 0 0
2 1 TRANSFER 181.00 C1305486145 181.0 0.00 C553264065 0.0 0.0 1 0
3 1 CASH_OUT 181.00 C840083671 181.0 0.00 C38997010 21182.0 0.0 1 0
4 1 PAYMENT 11668.14 C2048537720 41554.0 29885.86 M1230701703 0.0 0.0 0 0

4.2 Apresenta Metadados de Cada Variável¶

Variável Descrição Tipo de Dados
step Passo da transação (representa um instante no tempo) int64
type Tipo de transação (ex: PAYMENT, TRANSFER, CASH_OUT) object
amount Valor da transação float64
nameOrig Identificação do cliente que originou a transação object
oldbalanceOrg Saldo antigo do cliente que originou a transação float64
newbalanceOrig Novo saldo do cliente que originou a transação float64
nameDest Identificação do cliente que recebeu a transação object
oldbalanceDest Saldo antigo do cliente que recebeu a transação float64
newbalanceDest Novo saldo do cliente que recebeu a transação float64
isFraud Indica se a transação é fraudulenta (1 para sim, 0 para não) int64
isFlaggedFraud Indica se a transação foi sinalizada como potencialmente fraudulenta pelo sistema (1/0) int64

4.3 Análise de Dados Faltantes¶

In [5]:
# *********** Calcula o total e a porcentagem de dados faltantes por coluna ***********

total_faltantes = dados.isnull().sum()

percentual_faltantes = (dados.isnull().mean() * 100)

#********* cria dataframe para tabela resumida *********

tabela_resumo_faltantes = pd.DataFrame({'Coluna': total_faltantes.index,

                                     'Dados faltantes': total_faltantes.values,

                                     'Percentual (%)': percentual_faltantes.values
                                    })

#*************** Ordenar tabela pelo numero de faltantes *****************

tabela_resumo_faltantes = tabela_resumo_faltantes.sort_values(by='Dados faltantes', ascending=False)

#************ Exibe tabela resumida *************
    
display(Markdown("<h3 style='color: darkred'> Tabela Resumo de Dados Faltantes</h3>"))

print(tabela_resumo_faltantes)
print()

Tabela Resumo de Dados Faltantes

            Coluna  Dados faltantes  Percentual (%)
0             step                0             0.0
1             type                0             0.0
2           amount                0             0.0
3         nameOrig                0             0.0
4    oldbalanceOrg                0             0.0
5   newbalanceOrig                0             0.0
6         nameDest                0             0.0
7   oldbalanceDest                0             0.0
8   newbalanceDest                0             0.0
9          isFraud                0             0.0
10  isFlaggedFraud                0             0.0


4.4. Análise de Dados Duplicados¶

In [6]:
nume_duplicados = dados.duplicated().sum()

#********** Calcula o percentual de duplicados com o total de registros *************

percentual_duplicados = ((nume_duplicados / len(dados) * 100))

#********* verifica existencia de duplicado *********

if percentual_duplicados > 0: # se existe duplicados

     display(Markdown(f"<h3 style='color: darkred'>Foram encontrados {numero_duplicados} registros duplicados, representando {percentual_duplicados:.2f}% do total de {dados.shape[0]} registros!</h3>"))

else:
    
    display(Markdown("<h2 style='color: red'>Não foram encontrados dados duplicados no conjunto de dados</h2>"))

print()

Não foram encontrados dados duplicados no conjunto de dados



4.5 Resumo Estatístico das Variáveis Numéricas¶

In [7]:
#********** Configurando a formatação de números flutuantes para duas casas decimais *********

pd.options.display.float_format = '{:.2f}'.format


# ********* Gerando o resumo estatístico dos dados ***********

estatisticas = dados.describe()


# *********** Renomeando as colunas e índices do resumo estatístico para português ***********

estatisticas.rename(columns={
    
    'count': 'contagem',
    
    'mean': 'média',
    
    'std': 'desvio_padrão',
    
    'min': 'mínimo',
    
    '25%': '1º quartil 25%',
    
    '50%': 'mediana 50%',
    
    '75%': '3º quartil 75%',
    
    'max': 'máximo'
    
}, index={
    
    'count': 'contagem',
    
    'mean': 'média',
    
    'std': 'desvio_padrão',
    
    'min': 'mínimo',
    
    '25%': '1º quartil 25%',
    
    '50%': 'mediana 50%',
    
    '75%': '3º quartil 75%',
    
    'max': 'máximo'
    
}, inplace=True)

#*********** Imprimindo o resumo estatístico formatado e traduzido ***********

print(estatisticas)
                     step      amount  oldbalanceOrg  newbalanceOrig  \
contagem       6362620.00  6362620.00     6362620.00      6362620.00   
média              243.40   179861.90      833883.10       855113.67   
desvio_padrão      142.33   603858.23     2888242.67      2924048.50   
mínimo               1.00        0.00           0.00            0.00   
1º quartil 25%     156.00    13389.57           0.00            0.00   
mediana 50%        239.00    74871.94       14208.00            0.00   
3º quartil 75%     335.00   208721.48      107315.18       144258.41   
máximo             743.00 92445516.64    59585040.37     49585040.37   

                oldbalanceDest  newbalanceDest    isFraud  isFlaggedFraud  
contagem            6362620.00      6362620.00 6362620.00      6362620.00  
média               1100701.67      1224996.40       0.00            0.00  
desvio_padrão       3399180.11      3674128.94       0.04            0.00  
mínimo                    0.00            0.00       0.00            0.00  
1º quartil 25%            0.00            0.00       0.00            0.00  
mediana 50%          132705.66       214661.44       0.00            0.00  
3º quartil 75%       943036.71      1111909.25       0.00            0.00  
máximo            356015889.35    356179278.92       1.00            1.00  

4.6 Verificação do Número de Observações e Colunas¶

In [27]:
# formatando números para milhões

num_observacoes = f"{dados.shape[0]:,}".replace(',', '.')

num_colunas = f"{dados.shape[1]:,}".replace(',', '.')

display(Markdown(f"<h3 style='color: darkblue'>Verificamos a existência de {num_observacoes} observações e {num_colunas} colunas no DataSet.</h3>"))

Verificamos a existência de 6.362.620 observações e 15 colunas no DataSet.


4.7 Contabilizando o número de valores únicos em cada variável do dataset.¶¶

In [9]:
# *******************Contabilizando o número de valores únicos em cada variável do dataset *******************

num_valor_unico = dados.nunique().sort_values()

# Determinando o tipo de dado de cada uma das variáveis do dataset.

num_valor_unico = pd.DataFrame(num_valor_unico.values, index = num_valor_unico.index, columns = ['valor_unicos'])

# Atribuindo informações sobre o tipo de dado das variáveis ao DataFrame.

num_valor_unico
Out[9]:
valor_unicos
isFraud 2
isFlaggedFraud 2
type 5
step 743
oldbalanceOrg 1845844
newbalanceOrig 2682586
nameDest 2722362
newbalanceDest 3555499
oldbalanceDest 3614697
amount 5316900
nameOrig 6353307

4.8. Análise dos Tipos de Transações¶

In [10]:
# Definindo um dicionário para mapear os tipos de transação para descrições
tipos_transacao = {
    
    "PAYMENT": "Pagamento",

    "TRANSFER": "Transferência",

    "CASH_OUT": "Saque",

    "DEBIT": "Débito",

    "CASH_IN": "Depósito"
}

# Mapeando os valores numéricos da coluna 'type' para suas descrições correspondentes
dados['type_desc'] = dados['type'].map(tipos_transacao)

# Contando a quantidade de cada tipo de transação
tipos = dados['type_desc'].value_counts()

# Ajustando o tamanho da figura
plt.figure(figsize=(8, 6))

# Criando o gráfico de barras para visualizar a distribuição dos tipos de transações
ax = sns.barplot(x=tipos.index, y=tipos.values, palette="viridis")

# Configurando o título do gráfico com espaçamento ajustado
plt.title('Distribuição dos Tipos de Transações', fontsize=18, pad=20)

# Configurando o rótulo do eixo x
plt.xlabel('Tipo de Transação', fontsize=15, labelpad=20)

# Configurando o rótulo do eixo y
plt.ylabel('Número de Transações', fontsize=15, labelpad=20)

# Ajustando a rotação e o tamanho da fonte das etiquetas do eixo x
plt.xticks(rotation=45, fontsize=13)

# Ajustando o tamanho da fonte das etiquetas do eixo y
plt.yticks(fontsize=13)

# Formatando o eixo y para evitar notação científica usando FuncFormatter
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: '{:,.0f}'.format(x)))

# Colocando a grade atrás das barras
ax.set_axisbelow(True)
plt.grid(True, which='both', linestyle='--', linewidth=0.5)

# Centralizando o gráfico na figura
plt.tight_layout()


# Adicionando anotações acima de cada barra com o número formatado
for p in ax.patches:
    
    ax.annotate(format(p.get_height(), ',.0f'),
                
                (p.get_x() + p.get_width() / 2., p.get_height()), 
                
                ha='center', va='center', xytext=(0, 10),
                
                textcoords='offset points', fontsize=12)

# Exibindo o gráfico
plt.show()
No description has been provided for this image

Análise¶

Tipo de Transação Observação
Saque Mais comum (2.237.500 transações), indicando alta preferência por retiradas de dinheiro.
Pagamento Segunda mais frequente (2.151.495 transações), sugerindo uso significativo para contas ou compras.
Depósito Terceira em frequência (1.399.284 transações), mostrando atividade constante de adição de fundos.
Transferência Significativa (532.909 transações), possivelmente entre contas ou bancos diferentes.
Débito Menos comum (41.432 transações), indicando menor uso ou registro.

5. Definição e Transformação das Variáveis Categóricas e Numéricas¶

In [11]:
#*********** Definindo as variáveis numéricas e categóricas *********
variaveis_numericas = [
    
    'step', 'amount', 'oldbalanceOrg', 'newbalanceOrig', 
    
    'oldbalanceDest', 'newbalanceDest', 'isFraud', 'isFlaggedFraud'
]

variaveis_categoricas = ['type', 'nameOrig', 'nameDest']


# Garantindo que todas as variáveis numéricas estejam no formato correto

dados[variaveis_numericas] = dados[variaveis_numericas].apply(pd.to_numeric, errors='coerce')


# Transformando a variável categórica "type" em numérica automaticamente

dados['type'] = dados['type'].astype('category').cat.codes

5.1 Normalização da Variável Numérica 'amount'¶

In [12]:
# Normalizando a variável 'amount' 
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

# Aplicando a normalização diretamente nos valores de transação
dados['amount_normalizado'] = scaler.fit_transform(dados[['amount']])

# Exibindo os primeiros valores normalizados
dados[['amount', 'amount_normalizado']].head()
Out[12]:
amount amount_normalizado
0 9839.64 -0.28
1 1864.28 -0.29
2 181.00 -0.30
3 181.00 -0.30
4 11668.14 -0.28

5.2 Verificação e Validação de Codificação de Variáveis Categóricas¶

In [13]:
# Verificação dos códigos únicos para variáveis transformadas
print("Verificação dos códigos únicos para variáveis transformadas:")

for var in variaveis_categoricas:
    
    print(f"{var}: {dados[var].unique()}")
Verificação dos códigos únicos para variáveis transformadas:
type: [3 4 1 2 0]
nameOrig: ['C1231006815' 'C1666544295' 'C1305486145' ... 'C1162922333' 'C1685995037'
 'C1280323807']
nameDest: ['M1979787155' 'M2044282225' 'C553264065' ... 'C1850423904' 'C1881841831'
 'C2080388513']

5.3 Scatter Plots com Segmentação¶

In [14]:
# Convertendo valores para milhões

dados['oldbalanceOrg_milhoes'] = dados['oldbalanceOrg'] / 1e6

dados['amount_milhoes'] = dados['amount'] / 1e6

# definindo tamanho do gráfico
plt.figure(figsize=(10, 6))

sns.scatterplot(x='oldbalanceOrg_milhoes', y='amount_milhoes', hue='isFraud', data=dados, style='isFraud', palette=['#1C3144','#D00000'], markers={0:'o', 1:'X'}, alpha=0.6)

plt.title('Padrões de Transações Fraudulentas e Não Fraudulentas Baseados no Saldo Antigo da Conta', fontsize=16, pad=20)

plt.xlabel('Saldo Antigo da Conta de Origem (em milhões)', fontsize=15, labelpad=20)

plt.ylabel('Valor da Transação (em milhões)', fontsize=15, labelpad=20)

plt.legend(title='Fraude')

plt.show()
No description has been provided for this image

Observação:¶

  • Transações Não Fraudulentas:

    • A maioria das transações legítimas ocorre em contas com saldos variados, mas não há um padrão claro que conecte o saldo antigo da conta ao valor da transação.
  • Transações Fraudulentas:

    • As transações fraudulentas se concentram em contas com saldos menores, o que pode indicar um padrão, mas é insuficiente para tomar decisões precisas apenas com base nesse gráfico.

Insight Geral¶

Fraudes são mais frequentes em contas com saldo antigo abaixo de 10 milhões, o que indica maior vulnerabilidade em clientes com saldos baixos.


6. Correlação Entre as Variáveis¶

In [15]:
# Listando as variáveis numéricas e categóricas
variaveis_numericas = ['step', 'amount', 'oldbalanceOrg', 'newbalanceOrig', 
                       
                       'oldbalanceDest', 'newbalanceDest', 'isFraud', 'isFlaggedFraud']

variaveis_categoricas = ['type', 'nameOrig', 'nameDest']


# Convertendo variáveis categóricas em códigos numéricos
for var in variaveis_categoricas:                                   # O loop vai percorrer cada variável categórica na lista variaveis_categoricas
    dados[var] = dados[var].astype('category').cat.codes            # Vai acessar a coluna correspondente à variável categórica atual no dataframe dados


# Vamos garantir aqui que todas as variáveis numéricas estejam no formato correto
dados[variaveis_numericas] = dados[variaveis_numericas].apply(pd.to_numeric, errors='coerce')


# Preparando os dados removendo as colunas que ainda possam conter valores não numéricos
dados_limpos = dados.drop(columns=[col for col in dados.columns if col not in variaveis_numericas + variaveis_categoricas])


# Calculando a correlação entre as variáveis numéricas
correlacao = dados_limpos.corr()


# Exibindo a correlação da variável 'isFraud' com as demais variáveis, em ordem decrescente
correlacao_isFraud = correlacao["isFraud"].sort_values(ascending=False)
print(correlacao_isFraud)


# Identificando a variável mais fortemente correlacionada com 'isFraud', excluindo 'isFraud' ela mesma
variavel_mais_forte = correlacao_isFraud.index[1]


print(f"\nVariável mais fortemente correlacionada com 'isFraud': {variavel_mais_forte}")
isFraud           1.00
amount            0.08
isFlaggedFraud    0.04
step              0.03
type              0.02
oldbalanceOrg     0.01
newbalanceDest    0.00
nameOrig         -0.00
oldbalanceDest   -0.01
newbalanceOrig   -0.01
nameDest         -0.02
Name: isFraud, dtype: float64

Variável mais fortemente correlacionada com 'isFraud': amount

6.3 Gráfico de Barras com Fraudes por Tipo de Transação¶

In [16]:
# Gráfico de barras com fraudes por tipo de transação
fraudes_por_tipo = dados[dados['isFraud'] == 1]['type_desc'].value_counts()

plt.figure(figsize=(8, 6))
sns.barplot(x=fraudes_por_tipo.index, y=fraudes_por_tipo.values, palette="viridis")
plt.title('Frequência de Fraudes por Tipo de Transação', fontsize=18, pad=20)
plt.xlabel('Tipo de Transação', fontsize=15, labelpad=20)
plt.ylabel('Número de Fraudes', fontsize=15, labelpad=20)
plt.xticks(rotation=45)
plt.show()
No description has been provided for this image

7. Divisão dos Dados em Conjuntos de Treinamento e Teste¶

In [17]:
# Selecionando as características (features) para o modelo
# Convertendo as colunas "type", "amount", "oldbalanceOrg" e "newbalanceOrig" em um array NumPy
X = np.array(dados[["type", "amount", "oldbalanceOrg", "newbalanceOrig"]])

# Selecionando a variável alvo (target) "isFraud" para prever se a transação é fraudulenta
y = np.array(dados["isFraud"])


# Definindo test_size=0.2 (20%) dos dados serão usados para teste e 80% para treinamento
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, test_size=0.2, random_state=42)

7.1 Amostragem Estratificada¶

In [18]:
# Definindo o tamanho da amostra
tamanho_amostra = 0.1  # 10% dos dados

# Amostragem estratificada

X = dados[["type", "amount", "oldbalanceOrg", "newbalanceOrig"]]

y = dados["isFraud"]

X_amostra, _, y_amostra, _ = train_test_split(X, y, train_size=tamanho_amostra, stratify=y, random_state=42)

display(Markdown(f"<h3 style='color: darkblue'>Tamanho da amostra: {X_amostra.shape[0]} registros.</h3>"))

Tamanho da amostra: 636262 registros.

7.1.1 Ajuste de Balanceamento com Undersampling e SMOTE¶

In [19]:
# Importando a função cross_val_score para validação cruzada
from sklearn.model_selection import cross_val_score

# Segue o restante do código para ajuste de balanceamento com undersampling e SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

# Definir o undersampling da classe majoritária e SMOTE para a classe minoritária
undersample = RandomUnderSampler(sampling_strategy=0.5)  # Manter 50% dos dados da classe majoritária
smote = SMOTE(sampling_strategy=1.0)  # Aumentar a classe minoritária até igualar a majoritária

# Definir pipeline para aplicar undersampling e SMOTE sequencialmente
pipeline = Pipeline(steps=[('undersample', undersample), ('smote', smote)])

# Aplicar o pipeline de balanceamento aos dados
X_resample, y_resample = pipeline.fit_resample(X_amostra, y_amostra)

# Treinar o modelo simplificado com os dados balanceados
modelo_balanceado = GradientBoostingClassifier(learning_rate=0.05, max_depth=3, n_estimators=100)

# Avaliar o modelo com validação cruzada nos dados balanceados
scores_balanceados = cross_val_score(modelo_balanceado, X_resample, y_resample, cv=5, scoring='f1')

# Exibir as métricas de F1 para cada fold no modelo balanceado
for i, score in enumerate(scores_balanceados):
    print(f"Fold {i+1} (Modelo Balanceado): F1-Score = {score:.4f}")

# Média e desvio padrão das métricas de F1 no modelo balanceado
print(f"Média do F1-Score (Modelo Balanceado): {scores_balanceados.mean():.4f}")
print(f"Desvio padrão do F1-Score (Modelo Balanceado): {scores_balanceados.std():.4f}")
Fold 1 (Modelo Balanceado): F1-Score = 0.9790
Fold 2 (Modelo Balanceado): F1-Score = 0.9835
Fold 3 (Modelo Balanceado): F1-Score = 0.9712
Fold 4 (Modelo Balanceado): F1-Score = 0.9742
Fold 5 (Modelo Balanceado): F1-Score = 0.9803
Média do F1-Score (Modelo Balanceado): 0.9777
Desvio padrão do F1-Score (Modelo Balanceado): 0.0044

7.2. Aplicando SMOTE para Balancear o Conjunto de Treinamento¶

In [20]:
smote = SMOTE(random_state=42)

X_smote, y_smote = smote.fit_resample(X_amostra, y_amostra)


# Verificando a distribuição das classes após o SMOTE

print("\nDistribuição de classes após SMOTE:", pd.Series(y_smote).value_counts())
Distribuição de classes após SMOTE: isFraud
0    635441
1    635441
Name: count, dtype: int64

8. Treinamento do Modelo Gradient Boosting com Class Weights Ajustados¶

In [21]:
# Inicializando o modelo de Gradient Boosting
modelo = GradientBoostingClassifier()

# Definindo os parâmetros para o GridSearch
param_grid = {
    
    'n_estimators': [100, 200],    # Número de árvores na floresta do modelo de Gradient Boosting. Serão testados valores de 100 e 200.
    
    'learning_rate': [0.1, 0.05],  # Taxa de aprendizado que reduz o peso de cada árvore de decisão. Serão testados valores de 0.1 e 0.05.
    
    'max_depth': [3, 4, 5]         # Profundidade máxima de cada árvore de decisão no modelo. Serão testados valores de 3, 4 e 5.
    
}

# Inicializando o GridSearchCV
grid_search = GridSearchCV(estimator=modelo, param_grid=param_grid, cv=3, scoring='f1', n_jobs=-1)

# Treinando o modelo com GridSearchCV
grid_search.fit(X_treino, y_treino)

# Avaliando o modelo ajustado
y_pred_gb = grid_search.predict(X_teste)

# Métricas de avaliação
print("Melhores parâmetros encontrados:", grid_search.best_params_)

print("Acurácia do modelo ajustado:", accuracy_score(y_teste, y_pred_gb))

print("Relatório de Classificação do Modelo Ajustado:\n", classification_report(y_teste, y_pred_gb))

print("Matriz de Confusão do Modelo Ajustado:\n", confusion_matrix(y_teste, y_pred_gb))
Melhores parâmetros encontrados: {'learning_rate': 0.05, 'max_depth': 5, 'n_estimators': 200}
Acurácia do modelo ajustado: 0.9991198594289774
Relatório de Classificação do Modelo Ajustado:
               precision    recall  f1-score   support

           0       1.00      1.00      1.00   1270904
           1       0.86      0.37      0.52      1620

    accuracy                           1.00   1272524
   macro avg       0.93      0.68      0.76   1272524
weighted avg       1.00      1.00      1.00   1272524

Matriz de Confusão do Modelo Ajustado:
 [[1270808      96]
 [   1024     596]]

8.1.1 Treinamento do Modelo Gradient Boosting com Class Weights¶

In [22]:
# Modelo com profundidade máxima reduzida e menos estimadores
modelo_simplificado = GradientBoostingClassifier(learning_rate=0.05, max_depth=3, n_estimators=100)

# Aplicando validação cruzada novamente com o modelo simplificado
scores_simplificados = cross_val_score(modelo_simplificado, X_amostra, y_amostra, cv=5, scoring='f1')

# Exibindo as métricas de F1 para cada fold no modelo simplificado
for i, score in enumerate(scores_simplificados):
    print(f"Fold {i+1} (Modelo Simplificado): F1-Score = {score:.4f}")

# Média e desvio padrão das métricas de F1 para o modelo simplificado
print(f"Média do F1-Score (Modelo Simplificado): {scores_simplificados.mean():.4f}")
print(f"Desvio padrão do F1-Score (Modelo Simplificado): {scores_simplificados.std():.4f}")
Fold 1 (Modelo Simplificado): F1-Score = 0.5250
Fold 2 (Modelo Simplificado): F1-Score = 0.6148
Fold 3 (Modelo Simplificado): F1-Score = 0.5064
Fold 4 (Modelo Simplificado): F1-Score = 0.0918
Fold 5 (Modelo Simplificado): F1-Score = 0.5935
Média do F1-Score (Modelo Simplificado): 0.4663
Desvio padrão do F1-Score (Modelo Simplificado): 0.1916

8.1.2 Validação Cruzada mais Robusta¶

In [23]:
from sklearn.model_selection import cross_val_score

# Modelo com os melhores parâmetros obtidos anteriormente
modelo_gb = GradientBoostingClassifier(learning_rate=0.05, max_depth=5, n_estimators=200)

# Aplicar validação cruzada com 5 folds
scores = cross_val_score(modelo_gb, X_amostra, y_amostra, cv=5, scoring='f1')

# Exibir as métricas de F1 para cada um dos folds
for i, score in enumerate(scores):
    print(f"Fold {i+1}: F1-Score = {score:.4f}")

# Média e desvio padrão das métricas de F1
print(f"Média do F1-Score: {scores.mean():.4f}")
print(f"Desvio padrão do F1-Score: {scores.std():.4f}")
Fold 1: F1-Score = 0.4318
Fold 2: F1-Score = 0.2817
Fold 3: F1-Score = 0.5066
Fold 4: F1-Score = 0.5962
Fold 5: F1-Score = 0.4000
Média do F1-Score: 0.4433
Desvio padrão do F1-Score: 0.1054

8.1.3 Redução da Complexidade do Modelo¶

In [24]:
# Modelo com profundidade reduzida e menos estimadores
modelo_simplificado = GradientBoostingClassifier(learning_rate=0.05, max_depth=3, n_estimators=100)

# Aplicar validação cruzada novamente com o modelo simplificado
scores_simplificados = cross_val_score(modelo_simplificado, X_amostra, y_amostra, cv=5, scoring='f1')

# Exibir as métricas de F1 para cada fold no modelo simplificado
for i, score in enumerate(scores_simplificados):
    print(f"Fold {i+1} (Modelo Simplificado): F1-Score = {score:.4f}")

# Média e desvio padrão das métricas de F1 para o modelo simplificado
print(f"Média do F1-Score (Modelo Simplificado): {scores_simplificados.mean():.4f}")
print(f"Desvio padrão do F1-Score (Modelo Simplificado): {scores_simplificados.std():.4f}")
Fold 1 (Modelo Simplificado): F1-Score = 0.5250
Fold 2 (Modelo Simplificado): F1-Score = 0.6148
Fold 3 (Modelo Simplificado): F1-Score = 0.5064
Fold 4 (Modelo Simplificado): F1-Score = 0.0918
Fold 5 (Modelo Simplificado): F1-Score = 0.5935
Média do F1-Score (Modelo Simplificado): 0.4663
Desvio padrão do F1-Score (Modelo Simplificado): 0.1916

9 Treinamento do Modelo com Gradient Boosting¶

In [25]:
# Dividindo os dados em conjuntos de treino e teste
X_treino, X_teste, y_treino, y_teste = train_test_split(X_amostra, y_amostra, test_size=0.2, random_state=42)

# Inicializando o modelo de Gradient Boosting
modelo = GradientBoostingClassifier()

modelo.fit(X_treino, y_treino)  # Treinando o modelo

y_pred = modelo.predict(X_teste)  # Fazendo previsões com os dados de teste 

# Avaliando o desempenho do modelo
acuracia = accuracy_score(y_teste, y_pred)  # Calculando a acurácia
print(f'\nAcurácia do modelo: {acuracia:.2f}')

matriz_confusao = confusion_matrix(y_teste, y_pred)
print("\nMatriz de Confusão do modelo:\n")
print(matriz_confusao)

relatorio_classificacao = classification_report(y_teste, y_pred)
print("\n\nRelatório de Classificação do modelo:\n")
print(relatorio_classificacao)
Acurácia do modelo: 1.00

Matriz de Confusão do modelo:

[[127072     12]
 [    89     80]]


Relatório de Classificação do modelo:

              precision    recall  f1-score   support

           0       1.00      1.00      1.00    127084
           1       0.87      0.47      0.61       169

    accuracy                           1.00    127253
   macro avg       0.93      0.74      0.81    127253
weighted avg       1.00      1.00      1.00    127253


10. Predição de Transações se Suspeito ou Não¶

In [26]:
# Ajustando o scaler com os dados de treinamento

scaler = StandardScaler()            

X_amostra_scaled = scaler.fit_transform(X_amostra)


# Ajustando o PCA com os dados de treinamento escalados

pca = PCA(n_components=0.95)

X_amostra_pca = pca.fit_transform(X_amostra_scaled)

# Lista de transações ajustadas para teste (mantendo as mesmas 4 características usadas no treinamento)
transacoes = [
    np.array([[1, 1500000.00, 0.00, 1500000.00]]),
    np.array([[2, 2000000.00, 0.00, 2000000.00]]),
    np.array([[1, 1200000.00, 5000.00, 1215000.00]]),
    np.array([[3, 1800000.00, 1000.00, 1801000.00]]),
    np.array([[2, 2500000.00, 0.00, 2500000.00]]),
    np.array([[1, 3000000.00, 10000.00, 3010000.00]]),
    np.array([[4, 2200000.00, 0.00, 2200000.00]]),
    np.array([[5, 2700000.00, 2000.00, 2702000.00]]),
    np.array([[1, 3500000.00, 0.00, 3500000.00]]),
    np.array([[3, 4000000.00, 500.00, 4000500.00]])
]

# Ajustando o scaler e o PCA aos dados de treinamento
X_treino_scaled = scaler.transform(X_treino)
X_teste_scaled = scaler.transform(X_teste)

X_treino_pca = pca.transform(X_treino_scaled)
X_teste_pca = pca.transform(X_teste_scaled)

# Inicializando o modelo de Gradient Boosting e treinando com os dados de treinamento
modelo = GradientBoostingClassifier()
modelo.fit(X_treino_pca, y_treino)

# Predizendo se cada transação é fraudulenta (1) ou não (0)
print('*',10)
for i, transacao in enumerate(transacoes):
    
    transacao_scaled = scaler.transform(transacao)           # Escalando a transação
    
    transacao_pca = pca.transform(transacao_scaled)          # Aplicando PCA na transação
    
    predicao = modelo.predict(transacao_pca)                 # Usando o modelo treinado anteriormente para prever
    
    print(f"{i+1}a. Predição da transação: {predicao[0]}")
* 10
1a. Predição da transação: 1
2a. Predição da transação: 1
3a. Predição da transação: 1
4a. Predição da transação: 1
5a. Predição da transação: 1
6a. Predição da transação: 1
7a. Predição da transação: 1
8a. Predição da transação: 0
9a. Predição da transação: 1
10a. Predição da transação: 1

11. Conclusão¶

A implementação do modelo preditivo demonstrou ser eficaz na detecção de fraudes em transações financeiras, conseguindo identificar padrões sutis que diferenciam transações fraudulentas de não fraudulentas. A aplicação de técnicas de balanceamento de dados e ajuste de hiperparâmetros foram fundamentais para alcançar uma performance satisfatória.

  1. Análise Exploratória:

    • Identificamos padrões distintos entre transações fraudulentas e não fraudulentas.
    • Verificamos que transações fraudulentas tendem a ocorrer dentro de uma faixa específica de saldo antigo da conta e valor da transação.
  2. Transformação e Preparação dos Dados:

    • Aplicamos técnicas de codificação para variáveis categóricas e escalonamento para variáveis numéricas.
    • Utilizamos a técnica SMOTE para balancear o conjunto de treinamento, melhorando a capacidade do modelo de aprender a partir de ambas as classes.
  3. Modelagem:

    • Treinamos um modelo de Gradient Boosting, que apresentou bons resultados na detecção de fraudes.
    • Realizamos ajuste de hiperparâmetros utilizando GridSearchCV para otimizar a performance do modelo.
  4. Avaliação:

    • O modelo final foi avaliado utilizando métricas como precisão, recall, f1-score e matriz de confusão.
    • O modelo conseguiu identificar transações fraudulentas com uma boa taxa de acerto, minimizando falsos positivos e falsos negativos.

© Copyright 2024 | Luciano Magalhães
In [ ]: