数据集: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