神经网络的异常(异常值)检测
在过去的几周里,我一直在研究用来检测数据中的异常值的技术。根据问题和数据的类型,在应用程序中使用了不同的技术,我仍然在探索和学习这些技术。不管怎样,不久前我参加了一个关于深度学习的MOOC课程,其中一节讲的是用于非监督任务的神经网络模型。在使用的模型中,自动编码器被归类到属于非监督任务的模型中,这些模型在异常(异常值)检测中越来越流行。
我使用了以下的库和版本:
Numpy version : 1.14.2
Pandas version : 0.22.0
Matplotlib version : 2.0.2
Seaborn version : 0.7.1
Plotly version : 2.7.0
Tensorflow version : 1.8.0
Keras version : 2.2.0
Numpy,Pandas用于数据处理,Plotly用于可视化。当我使用基于神经网络的模型时,Keras使用tensorflow作为后端。
导入库:
import numpy as np
import pandas as pd
pd.set_option('display.max_columns', None)
pd.set_option('display.max_row', None)
import matplotlib.pyplot as plt
plt.rcdefaults()
from pylab import rcParams
import seaborn as sns
%matplotlib inline
import datetime
#################
#######Ploltly
import plotly
import plotly.plotly as py
import plotly.offline as pyo
import plotly.figure_factory as ff
from plotly.tools import FigureFactory as FF, make_subplots
import plotly.graph_objs as go
from plotly.graph_objs import *
from plotly import tools
from plotly.offline import download_plotlyjs, init_notebook_mode, iplot
import cufflinks as cf
init_notebook_mode(connected=True)
cf.go_offline()
pyo.offline.init_notebook_mode()
####### Deep learning libraries
import tensorflow as tf
import keras
from keras.models import Model, load_model
from keras.layers import Input, Dense
from keras.callbacks import ModelCheckpoint, TensorBoard
from keras import regularizers
from ann_visualizer.visualize import ann_viz
#
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import (confusion_matrix, classification_report, accuracy_score, roc_auc_score, auc,
precision_score, recall_score, roc_curve, precision_recall_curve,
precision_recall_fscore_support, f1_score,
precision_recall_fscore_support)
#
from IPython.display import display, Math, Latex
加载数据
数据集:https://github.com/abelusha/AutoEncoders-for-Anomaly-Detection/blob/master/JADS_CarrerDay_Data.cvs
df = pd.read_csv('JADS_CarrerDay_Data.cvs',index_col=0))
探索数据
df.shape
(4550, 10)
数据有4500多行和10个特性。让我们看一下数据的前5行。
df.head()
数据有9个数字特征,第10个特征是标签。特征“Label”上的pandas value_counts()提供了非常不平衡的数据,如下面的条形图所示
def label_dist(df):
x = df.Label.astype(str).unique().tolist()
y = df.Label.value_counts().tolist()
trace = {'type': 'bar',
'x' : x,
'y' : y,
'text' : [str(y[0]),str(y[1])],
'textposition' : "outside", 'textfont' : {'size' : 20}
}
data = Data([trace])
layout = {'title' : '<b>Data per Label', 'titlefont': {'size': 20},
'xaxis' : {'title':'<b>Labels', 'titlefont':{'size' : 20}, 'tickfont': {'size':20}},
'yaxis' : {'title':'<b>Frequency','titlefont':{'size' : 20}, 'tickfont': {'size':20}, 'range': [0,max(y) + 1000]},
}
fig = Figure(data = data, layout = layout)
return py.iplot(fig)
label_dist(df)
接下来将对每个特性的线和kde图进行更多的数据探索。
下面有两个函数用于自动绘制eac特性的图,line_plot、kde_hist。函数子图用于良好的可视化,使我们能够看到每个特性的行为。
I. line_plot()
def line_plot(df, col):
x = df.index.tolist()
y = df[col].tolist()
trace = {'type': 'scatter',
'x' : x,
'y' : y,
'mode' : 'markers',
'marker': {'colorscale':'red', 'opacity': 0.5}
}
data = Data([trace])
layout = {'title': 'Line plot of {}'.format(col), 'titlefont': {'size': 30},
'xaxis' : {'title' :'Data Index', 'titlefont': {'size' : 20}},
'yaxis' : {'title': col, 'titlefont' : {'size': 20}},
'hovermode': 'closest'
}
fig = Figure(data = data, layout = layout)
return fig
II. kde_hist()
def kde_hist(df, col):
name = [col]
hist_data = [df[col]]
fig = ff.create_distplot(hist_data,name, show_hist=False, bin_size= 0.5)
return fig
III. Subplots
def subplot(df, is_line = True):
fig_subplots = make_subplots(rows=10, cols=6, horizontal_spacing=0.2,vertical_spacing = 0.1, print_grid=False,
subplot_titles=(df.columns.tolist()),
specs = [[{'rowspan' : 2, 'colspan' : 3}, None,None, {'rowspan' : 2, 'colspan' : 3}, None,None ],
[None,None,None, None,None, None],
[{'rowspan' : 2, 'colspan' : 3}, None,None, {'rowspan' : 2,'colspan':3}, None,None ],
[None,None,None, None,None,None ],
[{'rowspan' : 2, 'colspan' : 3}, None,None, {'rowspan' : 2, 'colspan' : 3}, None,None ],
[None,None,None, None,None, None],
[{'rowspan' : 2, 'colspan' : 3}, None,None, {'rowspan' : 2,'colspan':3}, None,None ],
[None,None,None, None,None,None ],
[{'rowspan' : 2, 'colspan' : 3}, None,None, {'rowspan' : 2,'colspan':3}, None,None ],
[None,None,None, None,None,None ]
])
figs = {}
if(is_line):
n = 1
title = 'Line Plots of Variables'
for col in df.columns.tolist():
figs[col] = line_plot(df,col)
else:
n = 2
title = 'Normilized Distribution of Variables'
for col in df.columns.tolist():
figs[col] = kde_hist(df,col)
for i in range(n):
fig_subplots.append_trace(figs['V_1']['data'][i] , row = 1, col = 1)
fig_subplots.append_trace(figs['V_2']['data'][i] , row = 1, col = 4)
fig_subplots.append_trace(figs['V_3']['data'][i] , row = 3, col = 1)
fig_subplots.append_trace(figs['V_4']['data'][i] , row = 3, col = 4)
fig_subplots.append_trace(figs['V_5']['data'][i] , row = 5, col = 1)
fig_subplots.append_trace(figs['V_6']['data'][i] , row = 5, col = 4)
fig_subplots.append_trace(figs['V_7']['data'][i] , row = 7, col = 1)
fig_subplots.append_trace(figs['V_8']['data'][i] , row = 7, col = 4)
fig_subplots.append_trace(figs['V_9']['data'][i] , row = 9, col = 1)
fig_subplots.append_trace(figs['Label']['data'][0] , row = 9, col = 4)
fig_subplots['layout'].update({'title' : title, 'titlefont' : {'size' : 20}})
fig_subplots['layout'].update({ 'height' : 1500, 'width' : 1000, 'showlegend' : False})
return fig_subplots
9条特征线的图表显示,除了V_1和V_2以外,其余的不会遵循数据中的任何规律性
每个特征的分布图也表明数据描述有点复杂。除V_1为双峰之外,其余均不具有正态分布,如下图所示
就简单的EDA而言,数据是一个复杂的数据。从标签分布看,这个数据集非常不平衡。
我在这个项目中试图回答的问题是,我可以使用9个特征预测新数据点的标签吗?除此之外,我还有兴趣查看是否存在使用标签对数据集进行分类的功能趋势。
正如任何数据科学家一样,答案很简单明了。我们可以。唯一的区别是人们试图解决这个问题的数据科学技术。有了每个数据点的标签,第一个想到的机器学习算法或模型(毫无疑问)就是将这个问题当作监督分类问题来处理。然而,必须特别小心不平衡的数据集。处理不平衡数据集的文献中有不同的技术。不过在这篇文章中,我想把这个问题看作是无监督(半监督)任务。
自动编码器
正如我上面提到的,我想把这个问题视为无监督任务,特别是作为离群值(异常)检测任务。有不同的异常检测模型(算法),但我选择自动编码器有两个原因。首先,使用它们更简单,第二个原因是由于手头数据的复杂性。另外,在过去的几年里,他们在现实世界的问题中越来越受欢迎,比如在欺诈检测中。
它们是什么,为什么被使用
- 无监督任务的前馈神经网络模型(无标签)
- 模型恒等函数f(x)≈x(编码)
- 容易理解!
- 压缩数据并学习一些特性
下面讨论以下几点。
- 数据预处理
- 模型建立
- 模型评价
- 模型的解释
数据预处理
以下python行用于预处理数据。
RANDOM_SEED = 101
X_train, X_test = train_test_split(df, test_size=0.2, random_state = RANDOM_SEED)
X_train = X_train[X_train['Label'] == 0]
X_train = X_train.drop(['Label'], axis=1)
y_test = X_test['Label']
X_test = X_test.drop(['Label'], axis=1)
X_train = X_train.values
X_test = X_test.values
print('Training data size :', X_train.shape)
print('Validation data size :', X_test.shape)
可以看出,我将数据分为训练和测试(20%)。但是我使用的训练数据(X_train)只是标记为0(正常)的数据。此时,我将整个任务视为半监督任务。因此,我的模型将只对正常数据进行训练,其思想是,当我给它一个不正常的新数据时,模型应该将新数据作为异常数据(离群值)进行flagout 。
我还对数据进行标准化,以对所有特征提供同等重要性
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
模型建立
关于自动编码器的有趣之处是,模型的输入和输出是相同的
自编码器模型试图最小化重建误差(RE),即输入和输出之间的均方距离
Reconstruction Error
神经网络模型是由keras建立的。优化后的AE模型有5个完全连通的隐藏层,分别有6、3、2、3、6个神经元。输入和输出层有9个神经元,每个9个特征。我在所有的层中都使用了tangent hyperbolic (tanh)激活函数。我对模型进行了100个epochs的训练,使用 batch size 为50,训练过程中10%的训练数据用于验证。
# No of Neurons in each Layer [9,6,3,2,3,6,9]
input_dim = X_train.shape[1]
encoding_dim = 6
input_layer = Input(shape=(input_dim, ))
encoder = Dense(encoding_dim, activation="tanh",activity_regularizer=regularizers.l1(10e-5))(input_layer)
encoder = Dense(int(encoding_dim / 2), activation="tanh")(encoder)
encoder = Dense(int(2), activation="tanh")(encoder)
decoder = Dense(int(encoding_dim/ 2), activation='tanh')(encoder)
decoder = Dense(int(encoding_dim), activation='tanh')(decoder)
decoder = Dense(input_dim, activation='tanh')(decoder)
autoencoder = Model(inputs=input_layer, outputs=decoder)
autoencoder.summary()
_________________________________________
Layer (type) Output Shape Param #
=======================================
input_1 (InputLayer) (None, 9) 0
________________________________________
dense_1 (Dense) (None, 6) 60
________________________________________
dense_2 (Dense) (None, 3) 21
_______________________________________
dense_3 (Dense) (None, 2) 8
_______________________________________
dense_4 (Dense) (None, 3) 9
_______________________________________
dense_5 (Dense) (None, 6) 24
______________________________________
dense_6 (Dense) (None, 9) 63
====================================
Total params: 185
Trainable params: 185
Non-trainable params: 0
训练模型:
nb_epoch = 100
batch_size = 50
autoencoder.compile(optimizer='adam', loss='mse' )
t_ini = datetime.datetime.now()
history = autoencoder.fit(X_train_scaled, X_train_scaled,
epochs=nb_epoch,
batch_size=batch_size,
shuffle=True,
validation_split=0.1,
verbose=0
)
t_fin = datetime.datetime.now()
print('Time to run the model: {} Sec.'.format((t_fin - t_ini).total_seconds()))
df_history = pd.DataFrame(history.history)
模型评估
AE模型收敛得很好
def train_validation_loss(df_history):
trace = []
for label, loss in zip(['Train', 'Validation'], ['loss', 'val_loss']):
trace0 = {'type' : 'scatter',
'x' : df_history.index.tolist(),
'y' : df_history[loss].tolist(),
'name' : label,
'mode' : 'lines'
}
trace.append(trace0)
data = Data(trace)
layout = {'title' : 'Model train-vs-validation loss', 'titlefont':{'size' : 30},
'xaxis' : {'title': '<b> Epochs', 'titlefont':{ 'size' : 25}},
'yaxis' : {'title': '<b> Loss', 'titlefont':{ 'size' : 25}},
}
fig = Figure(data = data, layout = layout)
return pyo.iplot(fig)
train_validation_loss(df_history)
下一步是使用模型识别新数据中的异常值。为此,我使用测试数据(X_test)。因此,在某些阈值设置的基础上,重构误差越高,数据点就越高。设置阈值是一个非常困难的任务,需要领域知识和对数据本身的良好理解。
对于这个项目,我将阈值设置为0.1。
outliers = df_error.index[df_error.reconstruction_error > 0.1].tolist()
因此,现在被视为异常值的数据点列表如下:
outliers
[4073,
4053,
1383,
1336,
1387,
1391,
4065,
1398,
1381,
1330,
4048,
1328,
4063,
4054,
1329]
模型的解释
找到数据点作为离群值是一回事,但是知道是什么使它们成为离群值是另一回事。在下面的文章中,我将通过举例来解释模型的可解释性。NN模型的一个缺点是很难解释它们。对AEs来说情况有点不同。至少就我所知,对于这个数据集,我可以尝试解释为什么我的AE模型说一个数据点是一个离群值。如上所述,数据点的RE越高,它很可能是一个离群值。但是通过分解每个特性的贡献,我可以有额外的洞察力,这组特性对RE有很大的贡献。
下面的代码计算了每个特性的RE。
data_n = pd.DataFrame(X_test_scaled, index= y_test.index, columns=numerical_cols)
def compute_error_per_dim(point):
initial_pt = np.array(data_n.loc[point,:]).reshape(1,9)
reconstrcuted_pt = autoencoder.predict(initial_pt)
return abs(np.array(initial_pt - reconstrcuted_pt)[0])
上面的代码只是计算每个特性的实际数据点和预测数据点之间的距离。
下面的三个条形图显示了标记为oultler的数据点的RE - per特性
def bar_plot(df, data_pt):
x = df.columns.tolist()
y = df.loc[data_pt]
trace = {'type': 'bar',
'x' : x,
'y' : y}
data = Data([trace])
layout = {'title' : "<b>Reconstruction error in each dimension for data poitn {}".format(data_pt),
'titlefont':{'size' : 20},
'xaxis' : {'title': '<b>Features',
'titlefont':{'size' : 20},
'tickangle': -45, 'tickfont': {'size':15} },
'yaxis' : {'title': '<b>Reconstruction Error',
'titlefont':{'size' : 20},
'tickfont': {'size':15}},
'margin' : {'l':100, 'r' : 1, 'b': 200, 't': 100, 'pad' : 1},
'height' : 600, 'width' : 800,
}
fig = Figure(data = data, layout = layout)
return pyo.iplot(fig)
对于这三个数据点,最高的RE贡献来自特征V_1和V_2。因此,如果有办法调整我的功能,我应该更加重视V_1和V_2。
结论
我使用神经网络进行异常检测。AE是我在本文中没有讨论的很好的选择异常检测和降维。基于这个结果,为了减少离群点的数量,我应该更加重视功能V_1和V_2。在这方面,我发现AE的可解释性非常有用。