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

 

观察上图,能直观地看见有两个单独的集群,这看似是一个非常简单的任务,但是其实欺诈数据仅为黄色的点。仔细看的话,在较大的那个集群中,我们能够看见有三个黄色的点。因此,在我们保留欺诈数据的同时对正常数据进行了再次抽样。

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)
View Code

 

 

 

有上图可见,正常数据较为集中,类似于一个圆盘状,而欺诈数据则较为分散。此时,我们将构建一个自动编码器,它具有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()
View Code

 

 

 使用该模型,我们能够计算出正常交易时的均方根误差,并且还能知道当需要均方根误差值为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)
View Code

让我们选取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()
View Code

由上图可知,与正常交易数据相比,绝大部分欺诈数据均有较高的均方根误差,从而这个方法对欺诈数据的识别似乎非常奏效。

虽然我们放弃了5%的正常交易,但仍然存在低于阈值的欺诈交易。这或许可以通过使用更好的特征提取方法来进行改进,因为一些欺诈数据与正常交易数据具有非常相似的特征。例如,对于信用卡欺诈而言,如果交易是在不同国家发生的,那么比较有价值的特征是:前一小时、前一天、前一周的交易数量。

模型可视化

 

 

转载于:https://blog.csdn.net/m0_46510245/article/details/106895419