- Caso d’uso di data science nel banking: rilevamento delle frodi
- Preparare il dataset
- Calcolare le frodi nel dataset
- Usare SMOTE per riequilibrare i dati
- Applicare la regressione logistica
- Conclusione
Il banking è uno di quei settori fortunati in cui storicamente si raccolgono molti dati strutturati, ed è stato anche tra i primi ad adottare tecnologie di data science.
Come viene usata la data science nel banking? Oggi i dati sono diventati l’asset più prezioso in questo ambito. La data science è un requisito necessario per le banche che vogliono tenere il passo con i concorrenti, attrarre più clienti, aumentare la fedeltà di quelli esistenti, prendere decisioni più efficaci basate sui dati, potenziare il business, migliorare l’efficienza operativa, perfezionare servizi/prodotti esistenti e introdurne di nuovi, rafforzare la sicurezza e, di conseguenza, ottenere più ricavi. Non sorprende che la maggior parte della domanda di lavoro in data science provenga dal settore bancario.
La data science permette al settore bancario di svolgere con successo numerosi compiti, tra cui:
- analisi del rischio di investimento
- previsione del valore del cliente nel tempo (CLV)
- segmentazione della clientela
- previsione del churn dei clienti
- marketing personalizzato
- analisi del sentiment dei clienti
- assistenti virtuali e chatbot
Di seguito daremo un’occhiata più da vicino a uno dei casi d’uso più comuni della data science nel banking.
Caso d’uso di data science nel banking: rilevamento delle frodi
Le attività fraudolente rappresentano un problema impegnativo non solo nel banking ma anche in molti altri ambiti, come pubblica amministrazione, assicurazioni, settore pubblico, vendite e sanità. Qualsiasi attività che gestisce un gran numero di transazioni online è esposta a un rischio significativo di frode. I crimini finanziari possono assumere varie forme, tra cui transazioni fraudolente con carte di credito, assegni bancari falsificati, evasione fiscale, riciclaggio di denaro, attacchi informatici, furto di account dei clienti, identità sintetiche, domande false e truffe.
Il rilevamento delle frodi è un insieme di misure proattive adottate per identificare e prevenire attività fraudolente e perdite finanziarie. Le sue principali tecniche analitiche si dividono in due gruppi:
- Statistiche: calcolo di parametri statistici, regressione, distribuzioni di probabilità, data matching
- Intelligenza artificiale (AI): data mining, machine learning, deep learning
Il machine learning è un pilastro essenziale per il rilevamento delle frodi. La sua cassetta degli attrezzi offre due approcci:
- Metodi supervisionati: k-nearest neighbors, regressione logistica, support vector machines, decision tree, random forest, analisi delle serie temporali, reti neurali, ecc.
- Metodi non supervisionati: analisi dei cluster, analisi dei collegamenti, self-organizing maps, analisi delle componenti principali, riconoscimento delle anomalie, ecc.
Non esiste un algoritmo di machine learning universale e affidabile per il rilevamento delle frodi. Nei casi d’uso reali, si testano di solito diverse tecniche o loro combinazioni, si calcola l’accuratezza predittiva del modello e si seleziona l’approccio ottimale.
La sfida principale per i sistemi di rilevamento delle frodi è adattarsi rapidamente ai pattern di frode e alle tattiche dei truffatori in costante evoluzione e scoprire tempestivamente schemi nuovi e sempre più elaborati. I casi di frode sono sempre una minoranza e sono ben nascosti tra le transazioni reali.
Preparare il dataset
Esploriamo un’implementazione di machine learning per il rilevamento delle frodi su carte di credito usando il linguaggio Python. Lavoreremo sul dataset creditcard_data, un campione modificato da un dataset Kaggle su Credit Card Fraud Detection. I dati originali rappresentano transazioni effettuate con carte di credito intestate a titolari europei in due giorni di settembre 2013.
Importiamo i dati e diamo un’occhiata veloce:
import pandas as pd
creditcard_data = pd.read_csv('creditcard_data.csv', index_col=0)
print(creditcard_data.info())
print('\n')
pd.options.display.max_columns = len(creditcard_data)
print(creditcard_data.head(3))
<class 'pandas.core.frame.DataFrame'>
Int64Index: 5050 entries, 0 to 5049
Data columns (total 30 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 V1 5050 non-null float64
1 V2 5050 non-null float64
2 V3 5050 non-null float64
3 V4 5050 non-null float64
4 V5 5050 non-null float64
5 V6 5050 non-null float64
6 V7 5050 non-null float64
7 V8 5050 non-null float64
8 V9 5050 non-null float64
9 V10 5050 non-null float64
10 V11 5050 non-null float64
11 V12 5050 non-null float64
12 V13 5050 non-null float64
13 V14 5050 non-null float64
14 V15 5050 non-null float64
15 V16 5050 non-null float64
16 V17 5050 non-null float64
17 V18 5050 non-null float64
18 V19 5050 non-null float64
19 V20 5050 non-null float64
20 V21 5050 non-null float64
21 V22 5050 non-null float64
22 V23 5050 non-null float64
23 V24 5050 non-null float64
24 V25 5050 non-null float64
25 V26 5050 non-null float64
26 V27 5050 non-null float64
27 V28 5050 non-null float64
28 Amount 5050 non-null float64
29 Class 5050 non-null int64
dtypes: float64(29), int64(1)
memory usage: 1.2 MB
V1 V2 V3 V4 V5 V6 V7 \
0 1.725265 -1.337256 -1.012687 -0.361656 -1.431611 -1.098681 -0.842274
1 0.683254 -1.681875 0.533349 -0.326064 -1.455603 0.101832 -0.520590
2 1.067973 -0.656667 1.029738 0.253899 -1.172715 0.073232 -0.745771
V8 V9 V10 V11 V12 V13 V14 \
0 -0.026594 -0.032409 0.215113 1.618952 -0.654046 -1.442665 -1.546538
1 0.114036 -0.601760 0.444011 1.521570 0.499202 -0.127849 -0.237253
2 0.249803 1.383057 -0.483771 -0.782780 0.005242 -1.273288 -0.269260
V15 V16 V17 V18 V19 V20 V21 \
0 -0.230008 1.785539 1.419793 0.071666 0.233031 0.275911 0.414524
1 -0.752351 0.667190 0.724785 -1.736615 0.702088 0.638186 0.116898
2 0.091287 -0.347973 0.495328 -0.925949 0.099138 -0.083859 -0.189315
V22 V23 V24 V25 V26 V27 V28 \
0 0.793434 0.028887 0.419421 -0.367529 -0.155634 -0.015768 0.010790
1 -0.304605 -0.125547 0.244848 0.069163 -0.460712 -0.017068 0.063542
2 -0.426743 0.079539 0.129692 0.002778 0.970498 -0.035056 0.017313
Amount Class
0 189.00 0
1 315.17 0
2 59.98 0
Il dataset contiene le seguenti variabili:
- Variabili codificate numericamene da V1 a V28 che sono le componenti principali ottenute da una trasformazione PCA. Per motivi di riservatezza, non sono state fornite informazioni di background sulle feature originali.
- La variabile Amount rappresenta l’importo della transazione.
- La variabile Class indica se la transazione è stata una frode (1) oppure no (0).
Per loro natura, i casi di frode sono per fortuna una minoranza estrema in qualsiasi elenco di transazioni. Tuttavia, gli algoritmi di machine learning in genere funzionano meglio quando le diverse classi contenute nel dataset sono più o meno ugualmente presenti. In caso contrario, ci sono pochi dati da cui apprendere. Questo problema è detto squilibrio di classe.
Calcolare le frodi nel dataset
Calcoliamo la percentuale di transazioni fraudolente sul numero totale di transazioni nel nostro dataset:
round(creditcard_data['Class'].value_counts()*100/len(creditcard_data)).convert_dtypes()
0 99
1 1
Name: Class, dtype: Int64
e creiamo un grafico per visualizzare i punti dati di frode rispetto a quelli non fraudolenti:
import matplotlib.pyplot as plt
import numpy as np
def prep_data(df):
X = df.iloc[:, 1:28]
X = np.array(X).astype(float)
y = df.iloc[:, 29]
y = np.array(y).astype(float)
return X, y
def plot_data(X, y):
plt.scatter(X[y==0, 0], X[y==0, 1], label='Class #0', alpha=0.5, linewidth=0.15)
plt.scatter(X[y==1, 0], X[y==1, 1], label='Class #1', alpha=0.5, linewidth=0.15, c='r')
plt.legend()
return plt.show()
X, y = prep_data(creditcard_data)
plot_data(X, y)

Usare SMOTE per riequilibrare i dati
Possiamo ora confermare che la percentuale di transazioni fraudolente è molto bassa e che ci troviamo di fronte a un problema di squilibrio di classe. Per risolverlo, possiamo riequilibrare i dati usando la tecnica di oversampling della minoranza sintetica (SMOTE). Diversamente dall’oversampling casuale, SMOTE è leggermente più sofisticato perché non crea semplici copie identiche delle osservazioni. Invece, usa le caratteristiche dei vicini più prossimi dei casi di frode per creare nuovi campioni sintetici abbastanza simili alle osservazioni esistenti nella classe minoritaria. Applichiamo SMOTE ai nostri dati sulle carte di credito:
from imblearn.over_sampling import SMOTE
method = SMOTE()
X_resampled, y_resampled = method.fit_resample(X, y)
plot_data(X_resampled, y_resampled)

Come si vede, usando SMOTE otteniamo improvvisamente più osservazioni della classe minoritaria. Per vedere ancora meglio i risultati di questo approccio, li confronteremo con i dati originali:
def compare_plot(X, y, X_resampled, y_resampled, method):
f, (ax1, ax2) = plt.subplots(1, 2)
c0 = ax1.scatter(X[y==0, 0], X[y==0, 1], label='Class #0',alpha=0.5)
c1 = ax1.scatter(X[y==1, 0], X[y==1, 1], label='Class #1',alpha=0.5, c='r')
ax1.set_title('Original set')
ax2.scatter(X_resampled[y_resampled==0, 0], X_resampled[y_resampled==0, 1], label='Class #0', alpha=.5)
ax2.scatter(X_resampled[y_resampled==1, 0], X_resampled[y_resampled==1, 1], label='Class #1', alpha=.5,c='r')
ax2.set_title(method)
plt.figlegend((c0, c1), ('Class #0', 'Class #1'), loc='lower center', ncol=2, labelspacing=0.)
plt.tight_layout(pad=3)
return plt.show()
print(f'Original set:\n'
f'{pd.value_counts(pd.Series(y))}\n\n'
f'SMOTE:\n'
f'{pd.value_counts(pd.Series(y_resampled))}\n')
compare_plot(X, y, X_resampled, y_resampled, method='SMOTE')
Original set:
0.0 5000
1.0 50
dtype: int64
SMOTE:
0.0 5000
1.0 5000
dtype: int64

Quindi, il metodo SMOTE ha bilanciato completamente i nostri dati e la classe minoritaria ora è pari in dimensioni alla classe maggioritaria.
Torneremo presto all’applicazione pratica di SMOTE, ma per il momento riprendiamo i dati originali e proviamo a rilevare i casi di frode. Seguendo il metodo “old school”, dobbiamo creare alcune regole per intercettare le frodi. Tali regole possono riguardare, ad esempio, luoghi insoliti delle transazioni o una frequenza sospettamente elevata di transazioni. L’idea è definire valori soglia basati su statistiche comuni, spesso sulle medie delle osservazioni, e usare tali soglie sulle nostre feature per rilevare le frodi.
print(creditcard_data.groupby('Class').mean().round(3)[['V1', 'V3']])
V1 V3
Class
0 0.035 0.037
1 -4.985 -7.294
Nel nostro caso specifico, applichiamo le seguenti condizioni: V1 < -3 e V3 < -5. Poi, per stimare le prestazioni di questo approccio, confronteremo i casi di frode segnalati con quelli effettivi:
creditcard_data['flag_as_fraud'] = np.where(np.logical_and(creditcard_data['V1']<-3, creditcard_data['V3']<-5), 1, 0)
print(pd.crosstab(creditcard_data['Class'], creditcard_data['flag_as_fraud'], rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud 0 1
Actual Fraud
0 4984 16
1 28 22
Applicare la regressione logistica
Abbiamo rilevato 22 casi di frode su 50, ma non riusciamo a intercettare gli altri 28 e abbiamo ottenuto 16 falsi positivi. Vediamo se l’uso di tecniche di machine learning può superare questi risultati.
Ora implementeremo un semplice algoritmo di classificazione con regressione logistica sui nostri dati delle carte di credito per individuare le occorrenze fraudolente e poi visualizzeremo i risultati su una matrice di confusione:
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
lr = LogisticRegression()
lr.fit(X_train, y_train)
predictions = lr.predict(X_test)
print(pd.crosstab(y_test, predictions, rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud 0.0 1.0
Actual Fraud
0.0 1504 1
1.0 1 9
È importante notare che qui abbiamo meno osservazioni nella matrice di confusione perché usiamo solo il test set per calcolare i risultati del modello, cioè solo il 30% dell’intero dataset.
Abbiamo intercettato una percentuale più alta di casi di frode: 90% (9 su 10), rispetto al risultato precedente del 44% (22 su 50). Abbiamo anche molti meno falsi positivi rispetto a prima, quindi è un miglioramento.
Torniamo ora al problema dello squilibrio di classe discusso in precedenza ed esploriamo se possiamo migliorare ulteriormente i risultati di previsione combinando il modello di regressione logistica con il metodo di campionamento SMOTE. Per farlo in modo efficiente e in un colpo solo, dobbiamo definire una pipeline ed eseguirla sui nostri dati:
from imblearn.pipeline import Pipeline
# Defining which resampling method and which ML model to use in the pipeline
resampling = SMOTE()
lr = LogisticRegression()
pipeline = Pipeline([('SMOTE', resampling), ('Logistic Regression', lr)])
pipeline.fit(X_train, y_train)
predictions = pipeline.predict(X_test)
print(pd.crosstab(y_test, predictions, rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud 0.0 1.0
Actual Fraud
0.0 1496 9
1.0 1 9
Come si vede, nel nostro caso SMOTE non ha portato miglioramenti: abbiamo comunque intercettato il 90% delle occorrenze di frode e, per di più, abbiamo un numero leggermente più alto di falsi positivi. La spiegazione è che il ricampionamento non porta necessariamente a risultati migliori in tutti i casi. Quando i casi di frode sono molto dispersi nei dati, i loro vicini più prossimi non sono necessariamente anch’essi casi di frode, quindi usare SMOTE può introdurre bias.
Conclusione
Come possibile passo successivo, per aumentare l’accuratezza del modello di regressione logistica possiamo ottimizzare alcuni parametri dell’algoritmo. Si può anche considerare la k-fold cross-validation invece di dividere semplicemente il dataset in due parti. Infine, possiamo provare altri algoritmi di machine learning (ad esempio, decision tree o random forest) e vedere se producono risultati migliori.
Se vuoi approfondire gli aspetti teorici e tecnici dell’implementazione di modelli di rilevamento delle frodi, puoi esplorare i materiali del corso Fraud Detection in Python.


