- Przykład użycia data science w bankowości: wykrywanie nadużyć
- Przygotowanie zbioru danych
- Obliczanie odsetka nadużyć w zbiorze
- Użycie SMOTE do zrównoważenia danych
- Zastosowanie regresji logistycznej
- Wnioski
Bankowość to jedna z tych dziedzin, w których historycznie gromadzi się dużo danych ustrukturyzowanych, a także jedna z pierwszych, gdzie zastosowano technologie data science.
Jak wykorzystuje się data science w bankowości? Dziś dane stały się w tej sferze najcenniejszym zasobem. Data science to niezbędny warunek, by banki dotrzymywały kroku konkurentom, pozyskiwały więcej klientów, zwiększały lojalność obecnych, podejmowały trafniejsze decyzje oparte na danych, wzmacniały biznes, podnosiły efektywność operacyjną, ulepszały istniejące usługi/produkty i wprowadzały nowe, wzmacniały bezpieczeństwo, a w rezultacie tych działań zwiększały przychody. Nic dziwnego, że większość popytu na stanowiska w data science pochodzi z sektora bankowego.
Data science pozwala sektorowi bankowemu z powodzeniem realizować wiele zadań, w tym:
- analizę ryzyka inwestycyjnego
- prognozowanie wartości życiowej klienta (CLV)
- segmentację klientów
- prognozowanie wskaźnika odejść klientów (churn)
- marketing spersonalizowany
- analizę nastrojów klientów
- wirtualnych asystentów i chatboty
Poniżej przyjrzymy się bliżej jednemu z najczęstszych zastosowań data science w bankowości.
Przykład użycia data science w bankowości: wykrywanie nadużyć
Oszustwa stanowią trudny problem nie tylko w bankowości, ale i w wielu innych obszarach, takich jak administracja publiczna, ubezpieczenia, sektor publiczny, sprzedaż czy opieka zdrowotna. Każdy biznes obsługujący dużą liczbę transakcji online jest narażony na istotne ryzyko nadużyć. Przestępstwa finansowe przybierają różne formy: nieautoryzowane transakcje kartą kredytową, sfałszowane czeki bankowe, unikanie podatków, pranie pieniędzy, ataki cybernetyczne, kradzież kont klientów, tożsamości syntetyczne, fałszywe wnioski i oszustwa.
Wykrywanie nadużyć to zestaw działań prewencyjnych podejmowanych w celu identyfikacji i zapobiegania oszustwom oraz stratom finansowym. Główne techniki analityczne można podzielić na dwie grupy:
- Statystyczne: obliczanie parametrów statystycznych, regresja, rozkłady prawdopodobieństwa, dopasowywanie danych
- Sztuczna inteligencja (AI): eksploracja danych, uczenie maszynowe, głębokie uczenie
Uczenie maszynowe to kluczowy filar wykrywania nadużyć. Jego zestaw narzędzi oferuje dwa podejścia:
- Metody nadzorowane: k-najbliższych sąsiadów, regresja logistyczna, maszyny wektorów nośnych, drzewo decyzyjne, las losowy, analiza szeregów czasowych, sieci neuronowe itd.
- Metody nienadzorowane: analiza skupień, analiza powiązań, samoorganizujące się mapy, analiza głównych składowych, wykrywanie anomalii itd.
Nie ma uniwersalnego i niezawodnego algorytmu uczenia maszynowego do wykrywania nadużyć. W realnych projektach testuje się zwykle kilka technik lub ich kombinacje, oblicza się trafność predykcji modelu i wybiera optymalne podejście.
Główne wyzwanie dla systemów wykrywania nadużyć to szybka adaptacja do stale zmieniających się wzorców oszustw i taktyk oszustów oraz szybkie ujawnianie nowych, coraz bardziej złożonych schematów. Przypadki nadużyć zawsze stanowią mniejszość i są dobrze ukryte wśród prawdziwych transakcji.
Przygotowanie zbioru danych
Przeanalizujmy implementację wykrywania nadużyć na kartach kredytowych z wykorzystaniem Pythona. Będziemy pracować na zbiorze creditcard_data, który jest zmodyfikowaną próbką z Kaggle: Credit Card Fraud Detection. Oryginalne dane to transakcje dokonane kartami kredytowymi należącymi do europejskich posiadaczy kart w ciągu dwóch dni we wrześniu 2013 r.
Zaimportujmy dane i rzućmy na nie okiem:
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
Zbiór zawiera następujące zmienne:
- Numerycznie zakodowane zmienne V1–V28, które są głównymi składowymi uzyskanymi z transformacji PCA. Ze względu na poufność nie podano kontekstu dotyczącego oryginalnych cech.
- Zmienna Amount to kwota transakcji.
- Zmienna Class wskazuje, czy transakcja była nadużyciem (1), czy nie (0).
Z natury rzeczy przypadki nadużyć na szczęście stanowią skrajną mniejszość w każdej liście transakcji. Jednak algorytmy uczenia maszynowego zwykle działają najlepiej, gdy różne klasy w zbiorze danych są mniej więcej równie liczne. W przeciwnym razie jest niewiele danych, z których można się uczyć. Ten problem to niezrównoważenie klas.
Obliczanie odsetka nadużyć w zbiorze
Policzmy odsetek transakcji oszukańczych w stosunku do łącznej liczby transakcji w naszym zbiorze:
round(creditcard_data['Class'].value_counts()*100/len(creditcard_data)).convert_dtypes()
0 99
1 1
Name: Class, dtype: Int64
i stwórzmy wykres wizualizujący punkty danych klasy oszustwo vs. brak oszustwa:
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)

Użycie SMOTE do zrównoważenia danych
Widzimy teraz, że odsetek transakcji oszukańczych jest bardzo niski i mamy do czynienia z problemem niezrównoważenia klas. Aby go naprawić, możemy zrównoważyć dane przy użyciu techniki syntetycznego nadpróbkowania mniejszości (SMOTE). W przeciwieństwie do losowego nadpróbkowania, SMOTE jest nieco bardziej wyrafinowane, ponieważ nie tworzy dokładnych kopii obserwacji. Zamiast tego wykorzystuje cechy najbliższych sąsiadów przypadków nadużyć do tworzenia nowych, syntetycznych próbek podobnych do istniejących obserwacji w klasie mniejszościowej. Zastosujmy SMOTE do naszych danych kartowych:
from imblearn.over_sampling import SMOTE
method = SMOTE()
X_resampled, y_resampled = method.fit_resample(X, y)
plot_data(X_resampled, y_resampled)

Jak widać, użycie SMOTE nagle daje nam więcej obserwacji klasy mniejszościowej. Aby lepiej zobaczyć efekty, porównamy je z oryginalnymi danymi:
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

Metoda SMOTE całkowicie zrównoważyła nasze dane, a klasa mniejszościowa jest teraz tak liczna jak większościowa.
Wrócimy jeszcze do praktycznego zastosowania SMOTE, ale na razie wróćmy do oryginalnych danych i spróbujmy wykryć przypadki nadużyć. Idąc „starą szkołą”, musimy stworzyć pewne reguły, by je wychwycić. Takie reguły mogą dotyczyć np. nietypowych lokalizacji transakcji lub podejrzanie częstych operacji. Pomysł polega na zdefiniowaniu wartości progowych na podstawie statystyk, często średnich wartości obserwacji, i użyciu tych progów na naszych cechach do wykrywania nadużyć.
print(creditcard_data.groupby('Class').mean().round(3)[['V1', 'V3']])
V1 V3
Class
0 0.035 0.037
1 -4.985 -7.294
W naszym przypadku zastosujmy warunki: V1 < -3 oraz V3 < -5. Następnie, aby ocenić skuteczność tego podejścia, porównamy oznaczone przypadki nadużyć z rzeczywistymi:
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
Zastosowanie regresji logistycznej
Wykryliśmy 22 z 50 przypadków nadużyć, ale nie potrafimy wykryć pozostałych 28, a do tego mamy 16 fałszywych trafień. Zobaczmy, czy techniki uczenia maszynowego poprawią te wyniki.
Wdrożymy teraz prosty algorytm klasyfikacji regresją logistyczną na naszych danych kartowych, by zidentyfikować nadużycia, a następnie zwizualizujemy wyniki na macierzy pomyłek:
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
Warto zauważyć, że w macierzy pomyłek mamy tu mniej obserwacji, ponieważ używamy jedynie zbioru testowego do obliczenia wyników modelu, tj. tylko 30% całego zbioru danych.
Wychwyciliśmy wyższy odsetek nadużyć: 90% (9 z 10), w porównaniu z wcześniejszym wynikiem 44% (22 z 50). Mamy też znacznie mniej fałszywych trafień niż wcześniej, więc to poprawa.
Wróćmy teraz do problemu niezrównoważenia klas i sprawdźmy, czy możemy jeszcze bardziej poprawić wyniki predykcji, łącząc model regresji logistycznej z metodą próbkowania SMOTE. Aby zrobić to sprawnie i za jednym razem, zdefiniujemy potok i uruchomimy go na naszych danych:
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
Jak widać, w naszym przypadku SMOTE nie przyniósł poprawy: nadal wychwyciliśmy 90% przypadków nadużyć, a dodatkowo mamy nieco więcej fałszywych trafień. Wyjaśnienie jest takie, że ponowne próbkowanie niekoniecznie zawsze daje lepsze wyniki. Gdy przypadki nadużyć są silnie rozproszone w danych, ich najbliżsi sąsiedzi nie muszą być również nadużyciami, więc użycie SMOTE może wprowadzać bias.
Wnioski
Aby dalej zwiększyć trafność modelu regresji logistycznej, można dostroić część jego parametrów. Zamiast dzielić zbiór na dwie części, można też rozważyć walidację krzyżową k-krotną. Wreszcie, warto spróbować innych algorytmów uczenia maszynowego (np. drzew decyzyjnych lub lasów losowych) i sprawdzić, czy dadzą lepsze wyniki.
Jeśli chcesz dowiedzieć się więcej o teoretycznych i technicznych aspektach implementacji modeli do wykrywania nadużyć, zajrzyj do materiałów kursu Fraud Detection in Python.
