數據集:Kaggle中使用信用卡欺詐數據:該數據集包含有在2013年9月歐洲持卡人的信用卡交易信息。
這個數據集顯示了兩天內發生的交易,其中在284,807次交易中有492次為欺詐數據。這樣的數據集是相當不平衡的,其中正類(欺詐)數據占所有交易數據的0.172%。
數據挖掘
這雖然是一個非常不平衡的數據集,但是它也是一個很好的例子:對異常或欺詐進行識別驗證。
首先,我們需要通過主成分分析法將數據集維度由30維下降到3維,並畫出其對應的點狀圖。其中,該數據集共有32列,第一列為時間,29列為未知的數據,1列為交易金額和剩下1列為類別。需要說明的是,我們將忽略時間這一指標,因為它不是一個較為固定的指標。

def show_pca_df(df): x = df[df.columns[1:30]].to_numpy() y = df[df.columns[30]].to_numpy() x = preprocessing.MinMaxScaler().fit_transform(x) pca = decomposition.PCA(n_components=3) pca_result = pca.fit_transform(x) print(pca.explained_variance_ratio_) pca_df = pd.DataFrame(data=pca_result, columns=['pc_1', 'pc_2', 'pc_3']) pca_df = pd.concat([pca_df, pd.DataFrame({'label': y})], axis=1) ax = Axes3D(plt.figure(figsize=(8, 8))) ax.scatter(xs=pca_df['pc_1'], ys=pca_df['pc_2'], zs=pca_df['pc_3'], c=pca_df['label'], s=25) ax.set_xlabel("pc_1") ax.set_ylabel("pc_2") ax.set_zlabel("pc_3") plt.show() df = pd.read_csv('creditcard.csv') show_pca_df(df)
觀察上圖,能直觀地看見有兩個單獨的集群,這看似是一個非常簡單的任務,但是其實欺詐數據僅為黃色的點。仔細看的話,在較大的那個集群中,我們能夠看見有三個黃色的點。因此,在我們保留欺詐數據的同時對正常數據進行了再次抽樣。

df_anomaly = df[df[df.columns[30]] > 0] df_normal = df[df[df.columns[30]] == 0].sample(n=df_anomaly.size, random_state=1, axis='index') df = pd.concat([ df_anomaly, df_normal]) show_pca_df(df)
有上圖可見,正常數據較為集中,類似於一個圓盤狀,而欺詐數據則較為分散。此時,我們將構建一個自動編碼器,它具有3層編碼器和2層解碼器,具體如下:
自動編碼器將我們的數據編碼到一個子空間,並且在對數據進行歸一化時將其解碼為相應的特征。我們希望自動編碼器能夠學習到在歸一化轉換時的特征,並且在應用時這個輸入和輸出是類似的。而對於異常情況,由於它是欺詐數據,所以輸入和輸出將會明顯不同。
這種方法的好處是它允許使用無監督的學習方式,畢竟在我們通常所使用的數據中,大部分的數據均為正常交易數據。並且數據的標簽通常是難以獲得的,而且在某些情況下完全沒法使用,例如手動對數據進行標記往往存在人為認識偏差等問題。從而,在對模型進行訓練的過程中,我們只使用沒有標簽的正常交易數據。
接下來,讓我們下載數據並訓練自動編碼器:

import torch import numpy import pandas as pd import matplotlib.pyplot as plt import numpy as np import matplotlib.lines as lines from sklearn import datasets, decomposition, preprocessing, model_selection from keras import models, layers, activations, losses, optimizers, metrics from keras.callbacks import EarlyStopping # from tensorflow.keras import layers,Input,regularizers,Model,Sequential from sklearn.preprocessing import StandardScaler import seaborn as sns from keras.utils import plot_model df = pd.read_csv('SofaSofa_Anomaly.csv') x=df[df.columns[1:30]].to_numpy() y=df[df.columns[30]].to_numpy() #准備數據 df=pd.concat([pd.DataFrame(x),pd.DataFrame({'anomaly':y})],axis=1) # xx=pd.DataFrame({'anomaly':y}) # print(df[[True,True]]) normal_events = df[df['anomaly'] == 0] abnormal_events = df[df['anomaly'] == 1] normal_events = normal_events.loc[:, normal_events.columns != 'anomaly']#讀取整行 abnormal_events=abnormal_events.loc[:,abnormal_events.columns!='anomaly'] scaler=preprocessing.MinMaxScaler()#標准化sklearn中的這個歸一化是對列進行歸一化,公式: # X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0)) # X_scaled = X_std * (max - min) + min scaler.fit(df.drop('anomaly',1))#刪除anomaly列,擬合數據 scaled_data = scaler.transform(normal_events)#標准化 train_data, test_data = model_selection.train_test_split(scaled_data, test_size=0.2) n_features=x.shape[1] # print(n_features) #模型 encoder=models.Sequential(name='encoder')#通過Sequential建立encoder模型 encoder.add(layer=layers.Dense(units=20,activation=activations.relu,input_shape=[n_features])) #添加全連接層,輸出維度為20,輸入維度為29 encoder.add(layers.Dropout(0.1))#輸出單位按比例1 / (1 - rate)進行縮放,防止過擬合 encoder.add(layer=layers.Dense(units=10, activation=activations.relu))#再添加一層全連接層 encoder.add(layer=layers.Dense(units=5, activation=activations.relu)) decoder = models.Sequential(name='decoder')#建立decoder模型 decoder.add(layer=layers.Dense(units=10,activation=activations.relu,input_shape=[5])) decoder.add(layer=layers.Dense(units=20,activation=activations.relu)) decoder.add(layers.Dropout(0.1)) decoder.add(layer=layers.Dense(units=n_features,activation=activations.sigmoid)) autoencoder=models.Sequential([encoder,decoder])#模型線性組合 autoencoder.compile(#模型編譯 loss=losses.MSE,#均方誤差 optimizer=optimizers.Adam(),#優化器 metrics=[metrics.mean_squared_error]#衡量指標 ) #模型訓練 es = EarlyStopping(monitor='val_loss', min_delta=0.00001, patience=20, restore_best_weights=True) #提前停止(early stopping)是一種在使用諸如梯度下降之類的迭代優化方法時,可對抗過擬合的正則化方法。官方說明文檔中的一句話:當監視指標停止改進時,停止訓練。 # 1、monitor: 被監測的數據。z # 2、min_delta: 在被監測的數據中被認為是提升的最小變化, 例如,小於 min_delta 的絕對變化會被認為沒有提升。 # 3、patience: 在監測質量經過多少輪次沒有進度時即停止。如果驗證頻率 # 4、restore_best_weights: 是否從具有監測數量的最佳值的時期恢復模型權重。 如果為 False,則使用在訓練的最后一步獲得的模型權重。 history = autoencoder.fit(x=train_data, y=train_data, epochs=100, verbose=1, validation_data=[test_data, test_data], callbacks=[es]) #verbose: 0, 1 或 2。日志顯示模式。 0 = 安靜模式, 1 = 進度條, 2 = 每輪一行。 #callback:回調函數 #validation_data: 元組 (x_val,y_val) 或元組 (x_val,y_val,val_sample_weights),用來評估損失,以及在每輪結束時的任何模型度量指標。模型將不會在這個數據上進行訓練。 plt.plot(history.history['loss'])#history的關鍵字'val_loss', 'val_acc', 'loss', 'acc' plt.plot(history.history['val_loss']) plt.title('Model Loss') plt.ylabel('Loss') plt.xlabel('Epoch') plt.legend(['Train', 'Test'], loc='upper left') plt.show()
使用該模型,我們能夠計算出正常交易時的均方根誤差,並且還能知道當需要均方根誤差值為95%時,閾值應該設置為多少

train_predicted_x = autoencoder.predict(x=train_data) train_events_mse = losses.mean_squared_error(train_data, train_predicted_x) cut_off = np.percentile(train_events_mse, 95)#當均方誤差為95%時所要設置的閾值 print('cut_off:', cut_off)
讓我們選取100個欺詐數據和100個正常數據作為樣本,結合閾值能夠繪制如下圖:

plot_samples = 100 #測試 # normal event real_x = test_data[:plot_samples].reshape(plot_samples, n_features) predicted_x = autoencoder.predict(x=real_x) normal_events_mse = losses.mean_squared_error(real_x, predicted_x) normal_events_df = pd.DataFrame({ 'mse': normal_events_mse, 'n': np.arange(0, plot_samples), 'anomaly': np.zeros(plot_samples)}) # abnormal event abnormal_x = scaler.transform(abnormal_events)[:plot_samples].reshape(plot_samples, n_features) predicted_x = autoencoder.predict(x=abnormal_x) abnormal_events_mse = losses.mean_squared_error(abnormal_x, predicted_x) abnormal_events_df = pd.DataFrame({ 'mse': abnormal_events_mse, 'n': np.arange(0, plot_samples), 'anomaly': np.ones(plot_samples)}) mse_df = pd.concat([normal_events_df, abnormal_events_df]) plot = sns.lineplot(x=mse_df.n, y=mse_df.mse, hue=mse_df.anomaly) line = lines.Line2D( xdata=np.arange(0, plot_samples), ydata=np.full(plot_samples, cut_off), color='#CC2B5E', linewidth=1.5, linestyle='dashed') plot.add_artist(line) plt.title('Threshlold: {threshold}'.format(threshold=cut_off)) plt.show()
由上圖可知,與正常交易數據相比,絕大部分欺詐數據均有較高的均方根誤差,從而這個方法對欺詐數據的識別似乎非常奏效。
雖然我們放棄了5%的正常交易,但仍然存在低於閾值的欺詐交易。這或許可以通過使用更好的特征提取方法來進行改進,因為一些欺詐數據與正常交易數據具有非常相似的特征。例如,對於信用卡欺詐而言,如果交易是在不同國家發生的,那么比較有價值的特征是:前一小時、前一天、前一周的交易數量。
模型可視化
轉載於:https://blog.csdn.net/m0_46510245/article/details/106895419