【Python数据分析案例】(十一)——自编码器监测异常值
网盘截屏
▶全部源码和数据,请点击“支付下载”获取!支付后无网盘链接,请联系客服QQ:3345172409或1919588043(微信同号)☺
导读
与传统的监督学习不一样,这一篇主要是讲述自编码器模型的,是无监督学习,并且用于的任务不是分类或者回归,而是异常值的监测。
案例背景
需要从一堆网络流量特征监控的数据中寻找哪些可能是异常情况。
听着像分类问题对吧,但是和分类问题有很大不同,一是训练方式不一样,二是异常值情况通常是很少的,所以不能做分类模型,要做自监督模型。
自编码器是什么我就不多介绍了,总之原理就是把数据拿来编码压缩然后解码还原,比较重构还原出来的数据和原来的数据的差异,差的多的,误差大的,就可能是异常值。
代码实现
导入包
import pandas as pd from pandas.plotting import scatter_matrix import numpy as np import pickle import h5py import matplotlib.pyplot as plt import seaborn as sns from scipy import stats from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error import tensorflow as tf from tensorflow.keras.models import Model, load_model from tensorflow.keras.layers import Input, Dense from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping from tensorflow.keras import regularizers from tensorflow.keras.utils import plot_model %matplotlib inline sns.set(style='whitegrid', palette='muted', font_scale=1.5) RANDOM_SEED = 42 LABELS = ['normal.', 'ipsweep.']
1.准备数据
1.1读取数据
读取,查看训练集数据
kddCupTrain = pd.read_csv('kddCupTrain.csv',header=None) kddCupTest = pd.read_csv('kddCupTest.csv',header=None) print("Shape of kddCupTrain: ",kddCupTrain.shape) print("There are any missing values: ", kddCupTrain.isnull().values.any()) kddCupTrain.head(3)
可以看到有40列特征,最后一列是标签。
查看测试集
print("Shape of kddCupTest: ",kddCupTest.shape) print("There are any missing values: ", kddCupTest.isnull().values.any()) kddCupTest.head(3)
查看响应变量y的信息
#将 kddCupTrain 数据集的第41列重命名为 'Class' 并且应用到原数据集中 kddCupTrain.rename(columns={41:'Class'}, inplace=True) #使用 pandas.factorize() 函数将 'Class' 列中的值转化为数值编码,并返回编码后的结果和唯一值列表 codes,uniques=pd.factorize(kddCupTrain['Class']) #打印出唯一的类别标签 print(uniques) #将 'Class' 列的数值编码更新到原数据集中 kddCupTrain['Class'] = codes #统计每个类别的样本数量并按数量从大到小排序,然后打印 count_classes = kddCupTrain['Class'].value_counts(sort = True) print(count_classes)
画个图看看
count_classes.plot.bar(figsize=(5,3)) #画图
可以看到是极度不平衡的数据。
取出y
y = kddCupTrain['Class'] kddCupTrain = kddCupTrain.drop(['Class'], axis=1)
1.2删除无用信息列
#取值唯一的变量删除(如果有一列的值全部一样,也就是取值唯一的特征变量就可以删除了,因为每个样本没啥区别,对模型就没啥用)
for col in kddCupTrain.columns: if len(kddCupTrain[col].value_counts())==1: print(col) kddCupTrain.drop(col,axis=1,inplace=True)
这两列删除了
测试集也进行删除
kddCupTest=kddCupTest[kddCupTrain.columns] print(kddCupTrain.shape,kddCupTest.shape)
1.3训练集和测试集都进行独立热编码
kddCupTrain=pd.get_dummies(kddCupTrain) kddCupTest=pd.get_dummies(kddCupTest) print(kddCupTrain.shape,kddCupTest.shape)
开通看到数据独立热编码后维度不一样,需要统一一下
统一数据维度
for col in kddCupTrain.columns: if col not in kddCupTest.columns: kddCupTest[col]=0 kddCupTest=kddCupTest[kddCupTrain.columns] print(kddCupTrain.shape,kddCupTest.shape)
查看训练集前三行
kddCupTrain.head(3)
测试集前三行
kddCupTest.head(3)
1.4数据标准化
#数据标准化 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() scaler.fit(kddCupTrain) X_s = scaler.transform(kddCupTrain) X_test_s = scaler.transform(kddCupTest) print('训练数据形状:') print(X_s.shape,y.shape) print('测试数据形状:') print(X_test_s.shape)
查看数据信息(验证是不是标准化了)
print(X_s.mean()) print(X_s.std(ddof=0))
1.5将数据拆分为训练子集和验证子集
#划分训练集和验证集 from sklearn.model_selection import train_test_split X_train,X_val,y_train,y_val=train_test_split(X_s,y,test_size=0.2,random_state=RANDOM_SEED)
查看形状
print(‘Train: shape X’,X_train.shape,’, shape Y’,y_train.shape)
print(‘Val: shape X’,X_val.shape,’, shape Y’,y_val.shape)
1.6. 分离“正常”实例
X_trainNorm = X_train[y_train == 0] X_valNorm = X_val[y_val == 0] print(X_trainNorm.shape,X_valNorm.shape)
2. 构建模型
2.1.选择自编码器的架构
自己选择几层几个神经元。我这里是4个隐藏层,编码压缩从83到40到20,然后再解码从20到40到83还原。看这个层的张量形状就能看出来。
input_dim = X_trainNorm.shape[1] layer1_dim = 40 encoder_dim = 20 input_layer = Input(shape=(input_dim, )) encoder1 = Dense(layer1_dim, activation="relu")(input_layer) encoder2 = Dense(encoder_dim, activation="relu")(encoder1) decoder1 = Dense(layer1_dim, activation='relu')(encoder2) decoder2 = Dense(input_dim, activation='linear')(decoder1) print('input_layer: ',input_layer) print('encoder1',encoder1) print('encoder2',encoder2) print('decoder1',decoder1) print('decoder2',decoder2)
查看模型信息。
autoencoder = Model(inputs=input_layer, outputs=decoder2) autoencoder.summary()
对模型进行可视化,画出来
plot_model(autoencoder, to_file='fraud_encoder1.png',show_shapes=True,show_layer_names=True)
2.2. 拟合模型
批量大小64,训练轮数50.
nb_epoch = 50 batch_size = 64 autoencoder.compile(optimizer='adam', loss='mean_squared_error') checkpointer = ModelCheckpoint(filepath="model.h5",verbose=0,save_best_only=True) earlystopping = EarlyStopping(monitor='val_loss', patience=5, verbose=0) # 'patience' number of not improving epochs history = autoencoder.fit(X_trainNorm, X_trainNorm,epochs=nb_epoch, batch_size=batch_size,shuffle=True, validation_data=(X_valNorm, X_valNorm), verbose=1,callbacks=[checkpointer, #tensorboard, earlystopping]).history
有早停机制,所以24轮就停下来了。
查看损失图
plt.plot(history['loss']) plt.plot(history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train', 'val'], loc='upper right');
3. 评估
3.1. 从文件加载安装的自动编码器
autoencoder = load_model('model.h5')
3.2. 重建
对验证集数据进行评价 (下面的图都是验证集的)
重构预测
testPredictions = autoencoder.predict(X_val) X_val.shape,testPredictions.shape
真实数据和预测重构的数据形状一样,下面计算对比他们的误差。
3.3. 评估
testMSE = mean_squared_error(X_val.transpose(), testPredictions.transpose(), multioutput='raw_values') error_df = pd.DataFrame({'reconstruction_error': testMSE,'true_class': y_val}) error_df.head()
描述性统计一下
error_df.reconstruction_error.describe()
画图查看正常的情况重构误差的分布。
fig = plt.figure(figsize=(5,3),dpi=128) ax = fig.add_subplot(111) normal_error_df = error_df[(error_df['true_class']== 0) & (error_df['reconstruction_error'] < 10)] ax.hist(normal_error_df.reconstruction_error.values, bins=10);
可以看到误差基本都是0附近,说明正常情况下的重构误差都很小。
查看异常值的重构误差分布
fig = plt.figure(figsize=(5,3),dpi=128) ax = fig.add_subplot(111) fraud_error_df = error_df[(error_df['true_class']== 1) & (error_df['reconstruction_error'] < 10)] ax.hist(fraud_error_df.reconstruction_error.values, bins=10);
可以看到误差分布没那么极端了,有很多样本重构后有较大的误差,说明异常情况下的重构误差都会偏大。
计算AUC值
from sklearn.metrics import (confusion_matrix, auc, roc_curve, cohen_kappa_score, accuracy_score) fpr, tpr, thresholds = roc_curve(error_df.true_class, error_df.reconstruction_error) roc_auc = auc(fpr, tpr) plt.figure(figsize=(7,4),dpi=128) plt.title('Receiver Operating Characteristic') plt.plot(fpr, tpr, label='AUC = %0.4f'% roc_auc) plt.legend(loc='lower right') plt.plot([0,1],[0,1],'r--') plt.xlim([-0.001, 1]) plt.ylim([0, 1.001]) plt.ylabel('True Positive Rate') plt.xlabel('False Positive Rate') plt.show();
3.4 预测效果
threshold = normal_error_df.reconstruction_error.quantile(q=0.995) threshold
找到误差5%的分位数水平作为阈值,重构误差大于这个值就认为是异常情况。
筛序一下极端情况,方便画图
error_df=error_df[error_df['reconstruction_error']<3500] groups = error_df.groupby('true_class') fig, ax = plt.subplots() for name, group in groups: if name == 1: MarkerSize = 7 ; Color = 'orangered' ; Label = 'Fraud' ; Marker = 'd' else: MarkerSize = 3.5 ; Color = 'b' ; Label = 'Normal' ; Marker = 'o' ax.plot(group.index, group.reconstruction_error, linestyle='',color=Color,label=Label,ms=MarkerSize,marker=Marker) ax.hlines(threshold, ax.get_xlim()[0], ax.get_xlim()[1], colors="r", zorder=100, label='Threshold') ax.legend(loc='upper left', bbox_to_anchor=(0.95, 1)) plt.title("Probabilities of fraud for different classes") plt.ylabel("Reconstruction error") ; plt.xlabel("Data point index") plt.show()
可以看到,大于阈值的情况,有一些事正常的,也有异常的,分类没那么准确。
画混淆矩阵来进一步观察
y_pred = [1 if e > threshold else 0 for e in error_df.reconstruction_error.values] conf_matrix = confusion_matrix(error_df.true_class, y_pred) print(conf_matrix) plt.figure(figsize=(5, 5),dpi=108) sns.heatmap(conf_matrix, xticklabels=LABELS, yticklabels=LABELS, annot=True, fmt="d"); plt.title("Confusion matrix") plt.ylabel('True class') plt.xlabel('Predicted class') plt.show()
计算科恩指标和准确率
cohen_kappa_score(error_df.true_class, y_pred),accuracy_score(error_df.true_class, y_pred)
准确率还是高达98的,但是由于样本的极度不平衡,科恩指标较小。
4.测试集的预测,创建提交
预测重构
testPredictions = autoencoder.predict(X_test_s) X_test_s.shape,testPredictions.shape
计算误差
testMSE = mean_squared_error(X_test_s.transpose(), testPredictions.transpose(), multioutput='raw_values') result_df = pd.DataFrame({'reconstruction_error': testMSE})
画个图看看
result_df.plot.box()
有很多极大值,说明可能这些误差大的样本就是异常情况。
储存,就可以提交了
result_df.to_csv('result.csv')
(如果需要变成分类的结果(是否为异常值),就按照上面算出阈值,然后加一个判断映射为分类变量就行)