基于自编码器和LSTM自编码器的心电信号异常检测

157 篇文章 1 订阅
126 篇文章 5 订阅
import os
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt


from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split


import tensorflow as tf
from tensorflow.keras import layers,losses
from tensorflow.keras.layers import Input, Dense, LSTM, RepeatVector, TimeDistributed
from tensorflow.keras.models import Model
from tensorflow.keras.utils import plot_model


RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)
# Import the dataset files


ecg_train_file = 'ECG5000_TRAIN.csv'
ecg_test_file = 'ECG5000_TEST.csv'


train_data = pd.read_csv(ecg_train_file)
test_data = pd.read_csv(ecg_test_file)


df_train = pd.DataFrame(train_data)
df_test = pd.DataFrame(test_data)
df = pd.concat([df_train, df_test], ignore_index=True)
df = df.drop(labels='id', axis=1)
# 140 timesteps + target
new_columns = list(df.columns)
new_columns[-1] = 'target'
df.columns = new_columns
df

normal_class = 1
class_names = ['Normal','R on T','PVC','SP','UB']
df['target'].value_counts()
1    2919
2    1767
4     194
3      96
5      24
Name: target, dtype: int64
plt.figure(figsize=(12,8))
ax = sns.countplot(x=df.target)
ax.set_xticklabels(class_names);

def plot_time_series_class(data, class_name, ax, n_steps=10):
  '''
  This function plots an averaged time series for each class, 
  smoothed out with a standard deviation around it.
  '''


  time_series_df = pd.DataFrame(data) 
  
  # sliding window iterator rolling(win_size)
  smooth_path = time_series_df.rolling(n_steps).mean()
  path_deviation = 2 * time_series_df.rolling(n_steps).std()


  # smooth with a std under and over the line
  under_line = (smooth_path - path_deviation)[0]
  over_line = (smooth_path + path_deviation)[0]


  ax.plot(smooth_path, linewidth=2)
  ax.fill_between(path_deviation.index, under_line, over_line, alpha=.200)
  ax.set_title(class_name)
classes = df.target.unique()


fig, axs = plt.subplots(
  nrows=len(classes) // 3 + 1,
  ncols=3,
  sharey=True,
  figsize=(18, 8)
)


# plot for each class (1-5)
for i, cls in enumerate(classes):
  ax = axs.flat[i]
  data = df[df.target == cls] \
    .drop(labels='target', axis=1) \
    .mean(axis=0) \
    .to_numpy()    
  plot_time_series_class(data, class_names[i], ax)


fig.delaxes(axs.flat[-1])
fig.tight_layout();

X_train, X_test, y_train, y_test = train_test_split(df.values,
                                                    df.values[:,-1], 
                                                    test_size=0.33,
                                                    random_state=RANDOM_SEED)


X_test, X_val, y_test, y_val = train_test_split(X_test,
                                                    y_test, 
                                                    test_size=0.5,
                                                    random_state=RANDOM_SEED)
print('The shape of the training set: ', X_train.shape)
print('The shape of the validation set:  ', X_val.shape)
print('The shape of the test set:  ', X_test.shape)
The shape of the training set:  (3350, 141)
The shape of the validation set:   (825, 141)
The shape of the test set:   (825, 141)
scaler = MinMaxScaler()
data_scaled = scaler.fit(X_train[:,:-1])
X_train[:,:-1] = data_scaled.transform(X_train[:,:-1])
X_val[:,:-1] = data_scaled.transform(X_val[:,:-1])
X_test[:,:-1] = data_scaled.transform(X_test[:,:-1])
df_train = pd.DataFrame(X_train)
df_val = pd.DataFrame(X_val)
df_test = pd.DataFrame(X_test)
def filter_normal_signals(df):
  '''Filters out normal signals (target = 1)'''
  
  df = df[df.iloc[:,-1] == 1].drop(columns=df.columns[-1], axis=1)
  return df.values


def filter_anomalous_signals(df):
  ''' Filters out anomalous signals (target > 1)'''


  df = df[df.iloc[:,-1] > 1].drop(columns=df.columns[-1], axis=1)
  return df.values
normal_X_train = filter_normal_signals(df_train)
normal_X_val = filter_normal_signals(df_val) 
normal_X_test = filter_normal_signals(df_test)
print('The shape of the normal signals training set: ', normal_X_train.shape)
print('The shape of the normal signals validation set:  ', normal_X_val.shape)
print('The shape of the normal signals test set:  ', normal_X_test.shape)
The shape of the normal signals training set:  (1932, 140)
The shape of the normal signals validation set:   (492, 140)
The shape of the normal signals test set:   (495, 140)
anomaly_X_train = filter_anomalous_signals(df_train)
anomaly_X_val = filter_anomalous_signals(df_val) 
anomaly_X_test = filter_anomalous_signals(df_test)
print('The shape of the normal signals training set: ', anomaly_X_train.shape)
print('The shape of the normal signals validation set:  ', anomaly_X_val.shape)
print('The shape of the normal signals test set:  ', anomaly_X_test.shape)
The shape of the normal signals training set:  (1418, 140)
The shape of the normal signals validation set:   (333, 140)
The shape of the normal signals test set:   (330, 140)
# normal signals


plt.figure(figsize=(12,8)) 
for i in range(5,10):
  plt.plot(normal_X_train[i])

# anomalous signals


plt.figure(figsize=(12,8)) 
for i in range(5,10):
  plt.plot(anomaly_X_train[i])

class AutoEncoder(Model):
  def __init__(self):
    super(AutoEncoder, self).__init__()
    # Define the encoder 
    self.encoder = tf.keras.Sequential([
      layers.Dense(64, activation="relu"),
      layers.Dense(32, activation="relu"),
      layers.Dense(16, activation="relu"),
      layers.Dense(8, activation="relu")])


    # Define the decoder 
    self.decoder = tf.keras.Sequential([
      layers.Dense(16, activation="relu"),
      layers.Dense(32, activation="relu"),
      layers.Dense(64, activation="relu"),
      layers.Dense(140, activation="sigmoid")])


  def call(self, x):
    # Define how an evaluation of the network is performed
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

autoencoder = AutoEncoder()


early_stopping = tf.keras.callbacks.EarlyStopping(
                    monitor='val_loss', 
                    patience=5,
                    mode='min',
                    restore_best_weights=True)


lr_plateau = tf.keras.callbacks.ReduceLROnPlateau(
    monitor='val_loss', 
    factor=0.5,
    patience=5, 
    min_lr=1e-6, 
    mode='min')


autoencoder.compile(optimizer='adam', loss='mse')
history = autoencoder.fit(normal_X_train, normal_X_train, 
                    epochs=30,
                    batch_size=128,
                    validation_data=(normal_X_val, normal_X_val),
                    callbacks=[early_stopping, lr_plateau],
                    shuffle=True)
autoencoder.decoder.summary()
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_4 (Dense)             (None, 16)                144       
                                                                 
 dense_5 (Dense)             (None, 32)                544       
                                                                 
 dense_6 (Dense)             (None, 64)                2112      
                                                                 
 dense_7 (Dense)             (None, 140)               9100      
                                                                 
=================================================================
Total params: 11,900
Trainable params: 11,900
Non-trainable params: 0
plt.figure(figsize=(12,8)) 
plt.plot(history.history["loss"], label="Training Loss")
plt.plot(history.history["val_loss"], label="Validation Loss")
plt.legend()

encoded_signal = autoencoder.encoder(normal_X_test).numpy()
decoded_signal = autoencoder.decoder(encoded_signal).numpy()


plt.figure(figsize=(12,8)) 
plt.plot(normal_X_test[100], 'b')
plt.plot(decoded_signal[100], 'r')
plt.fill_between(np.arange(140), decoded_signal[100], normal_X_test[100], color='lightcoral')
plt.legend(labels=["Input", "Reconstruction", "Error"])
plt.show()

encoded_signal = autoencoder.encoder(anomaly_X_test).numpy()
decoded_signal = autoencoder.decoder(encoded_signal).numpy()


plt.figure(figsize=(12,8)) 
plt.plot(anomaly_X_test[100], 'b')
plt.plot(decoded_signal[100], 'r')
plt.fill_between(np.arange(140), decoded_signal[100], anomaly_X_test[100], color='lightcoral')
plt.legend(labels=["Input", "Reconstruction", "Error"])
plt.show()

reconstruction = autoencoder.predict(normal_X_train)
train_loss = tf.keras.losses.mse(reconstruction, normal_X_train)
fig, ax = plt.subplots(figsize=(12, 8))
sns.distplot(train_loss, bins=50, kde=True, ax=ax.grid()).set(title='Distribution of normal train data reconstruction loss')

mean_normal = np.mean(train_loss)
std_normal = np.std(train_loss)
threshold = mean_normal + 2*std_normal
print("Threshold: ", threshold)
Threshold:  0.008762959466782183
reconstruction_test = autoencoder.predict(normal_X_test)
test_loss_normal = tf.keras.losses.mse(reconstruction_test, normal_X_test)
fig, ax = plt.subplots(figsize=(12, 8))
sns.distplot(test_loss_normal, bins=50, kde=True, ax=ax.grid()).set(title='Distribution of normal test data reconstruction loss')

reconstruction_anomalies = autoencoder.predict(anomaly_X_test)
test_loss_anomalies = tf.keras.losses.mse(reconstruction_anomalies, anomaly_X_test)
fig, ax = plt.subplots(figsize=(12, 8))
sns.distplot(test_loss_anomalies, bins=50, kde=True, ax=ax.grid()).set(title='Distribution of anomalous test data reconstruction loss')

sns.set(rc={'figure.figsize':(12,8)})


# Plot two histograms of normal and anomalous data
sns.distplot(test_loss_normal.numpy(), bins=50, label='normal')
sns.distplot(test_loss_anomalies.numpy(), bins=50, label='anomaly')


# Plot a vertical line representing the threshold
plt.axvline(threshold, color='r', linewidth=2, linestyle='dashed', label='{:0.3f}'.format(threshold))


# Add legend and title
plt.legend(loc='upper right')
plt.title('Threshold for detecting anomalies')
plt.show()

def predict_metrics(normal, anomalous, threshold):
  ''' Compute the True Positives (TP), True Negatives (TN), 
      False Positives (FP) and False Negatives (FN) given the 
      normal data, anomalous data and the threshold.
  '''
    # We correctly detect normal data if the normal loss is smaller than the threshold
    pred_normal = tf.math.less(normal, threshold)
    
    # We correctly detect anomalous data if the normal loss is greater than the threshold
    pred_anomaly = tf.math.greater(anomalous, threshold)


    tn = tf.math.count_nonzero(pred_normal).numpy()
    fp = normal.shape[0] - tn


    tp = tf.math.count_nonzero(pred_anomaly).numpy()
    fn = anomalous.shape[0] - tp
    
    return tp, tn, fp, fn
tp, tn, fp, fn = predict_metrics(test_loss_normal, test_loss_anomalies, threshold)


cm = [[tn, fp], 
      [fn, tp]]
categories = ['Normal', 'Anomalies']


plt.figure(figsize=(8,6))
g = sns.heatmap(cm, annot=True, fmt="d",
            xticklabels=categories, 
            yticklabels=categories, 
            cmap="GnBu")


g.set(xlabel='Predicted', ylabel='Actual', title='Confusion Matrix')


plt.show()

tpr_val = []
fpr_val = []


for t in np.linspace(0, 1, 100):
    tp, tn, fp, fn = predict_metrics(test_loss_normal, 
                                    test_loss_anomalies, 
                                    t/10)
    tpr = tp/(tp + fn)
    fpr = fp/(fp + tn)
    tpr_val.append(tpr)
    fpr_val.append(fpr)


fig, ax = plt.subplots(figsize=(10,7))
ax.plot(fpr_val, tpr_val)
ax.plot(np.linspace(0, 1, 100),
         np.linspace(0, 1, 100),
         label='baseline',
         linestyle='--')
plt.title('Receiver Operating Characteristic Curve', fontsize=18)
plt.ylabel('TPR', fontsize=16)
plt.xlabel('FPR', fontsize=16)
plt.legend(fontsize=12);

def compute_stats(tp, tn, fp, fn):
  ''' Computes accuracy, precision, recall and f1 scores
  given TP, TN, FP and FN'''
  
  acc = (tp + tn) / (tp + tn + fp + fn)
  prec = tp / (tp + fp) 
  rec = tp / (tp + fn)
  f1 = 2 * (prec * rec) / (prec + rec)


  return acc, prec, rec, f1
tp, tn, fp, fn = predict_metrics(test_loss_normal, test_loss_anomalies, threshold)
accuracy, precision, recall, f1 = compute_stats(tp, tn, fp, fn)


print("Accuracy = {}".format(np.round(accuracy,4)))
print("Precision = {}".format(np.round(precision,4)))
print("Recall = {}".format(np.round(recall,4)))
print("F1-score = {}".format(np.round(f1,4)))
Accuracy = 0.9745
Precision = 0.9558
Recall = 0.9818
F1-score = 0.9686
class LSTMAutoencoder(Model):
  def __init__(self):
    super(LSTMAutoencoder, self).__init__()
    self.encoder = tf.keras.Sequential([
        layers.Reshape((normal_X_train.shape[1], 1)),
        layers.LSTM(128, input_shape=(normal_X_train.shape[1], 1), return_sequences=True),
        layers.LSTM(64, return_sequences=False),
    ])


    self.repeat_vector = layers.RepeatVector(normal_X_train.shape[1])


    self.decoder = tf.keras.Sequential([
        layers.LSTM(64, return_sequences=True),
        layers.LSTM(128, return_sequences=True),
        layers.TimeDistributed(layers.Dense(1)),
        layers.Reshape((normal_X_train.shape[1],))
    ])


  def call(self, x):
    encoded = self.encoder(x)
    repeated = self.repeat_vector(encoded)
    decoded = self.decoder(repeated)
    return decoded
LSTMautoencoder = LSTMAutoencoder()


early_stopping = tf.keras.callbacks.EarlyStopping(
                    monitor='val_loss', 
                    patience=5,
                    mode='min',
                    verbose=1,
                    restore_best_weights=True)


lr_plateau = tf.keras.callbacks.ReduceLROnPlateau(
    monitor='val_loss', 
    factor=0.5, 
    patience=5, 
    verbose=1,
    mode='min')
LSTMautoencoder.compile(optimizer='adam', loss='mse')


history =  LSTMautoencoder.fit(normal_X_train, normal_X_train, 
                               epochs=20, 
                               validation_data=(normal_X_val, normal_X_val),
                               callbacks=[early_stopping, lr_plateau], 
                               shuffle=True)
plt.figure(figsize=(12,8)) 
plt.plot(history.history["loss"], label="Training Loss")
plt.plot(history.history["val_loss"], label="Validation Loss")
plt.legend()

encoded_signal_lstm = LSTMautoencoder.encoder(normal_X_test).numpy()
repeated_signal_lstm = LSTMautoencoder.repeat_vector(encoded_signal_lstm).numpy()
decoded_signal_lstm = LSTMautoencoder.decoder(repeated_signal_lstm).numpy()


plt.figure(figsize=(12,8)) 
plt.plot(normal_X_test[100], 'b')
plt.plot(decoded_signal_lstm[100], 'r')
plt.fill_between(np.arange(140), decoded_signal_lstm[100], normal_X_test[100], color='lightcoral')
plt.legend(labels=["Input", "Reconstruction", "Error"])
plt.show()

encoded_data_lstm = LSTMautoencoder.encoder(anomaly_X_test).numpy()
repeated_signal_lstm = LSTMautoencoder.repeat_vector(encoded_signal_lstm).numpy()
decoded_data_lstm = LSTMautoencoder.decoder(repeated_signal_lstm).numpy()


plt.figure(figsize=(12,8)) 
plt.plot(anomaly_X_test[100], 'b')
plt.plot(decoded_data_lstm[100], 'r')
plt.fill_between(np.arange(140), decoded_data_lstm[100], anomaly_X_test[100], color='lightcoral')
plt.legend(labels=["Input", "Reconstruction", "Error"])
plt.show()

reconstruction = LSTMautoencoder.predict(normal_X_train)
train_loss = tf.keras.losses.mse(reconstruction, normal_X_train)
fig, ax = plt.subplots(figsize=(12, 8))
sns.distplot(train_loss, bins=50, kde=True, ax=ax.grid()).set(title='Distribution of normal train data reconstruction loss')

mean_normal = np.mean(train_loss)
std_normal = np.std(train_loss)
threshold = mean_normal + std_normal
print("Threshold: ", threshold)
Threshold:  0.009467508550873416
reconstruction_test = LSTMautoencoder.predict(normal_X_test)
test_loss_normal = tf.keras.losses.mse(reconstruction_test, normal_X_test)
fig, ax = plt.subplots(figsize=(12, 8))
sns.distplot(test_loss_normal, bins=50, kde=True, ax=ax.grid()).set(title='Distribution of normal test data reconstruction loss')

reconstruction_anomalies = LSTMautoencoder.predict(anomaly_X_test)
test_loss_anomalies = tf.keras.losses.mse(reconstruction_anomalies, anomaly_X_test)
fig, ax = plt.subplots(figsize=(12, 8))
sns.distplot(test_loss_anomalies, bins=50, kde=True, ax=ax.grid()).set(title='Distribution of anomalous test data reconstruction loss')

sns.set(rc={'figure.figsize':(12,8)})


# Plot two histograms of normal and anomalous data
sns.distplot(test_loss_normal.numpy(), bins=50, label='normal')
sns.distplot(test_loss_anomalies.numpy(), bins=50, label='anomaly')


# Plot a vertical line representing the threshold
plt.axvline(threshold, color='r', linewidth=2, linestyle='dashed', label='{:0.3f}'.format(threshold))


# Add legend and title
plt.legend(loc='upper right')
plt.title('Threshold for detecting anomalies')
plt.show()

tp, tn, fp, fn = predict_metrics(test_loss_normal, test_loss_anomalies, threshold)


cm = [[tn, fp], 
      [fn, tp]]


categories = ['Normal', 'Anomalies']


plt.figure(figsize=(8,6))
g = sns.heatmap(cm, annot=True, fmt="d",
            xticklabels=categories, 
            yticklabels=categories,
            cmap="GnBu")


g.set(xlabel='Predicted', ylabel='Actual', title='Confusion Matrix')


plt.show()

tpr_val = []
fpr_val = []


for t in np.linspace(0, 1, 100):
    tp,tn,fp,fn = predict_metrics(test_loss_normal, 
                                  test_loss_anomalies, 
                                  t/10)
    tpr = tp/(tp + fn)
    fpr = fp/(fp + tn)
    tpr_val.append(tpr)
    fpr_val.append(fpr)


fig, ax = plt.subplots(figsize=(10,7))
ax.plot(fpr_val, tpr_val)
ax.plot(np.linspace(0, 1, 100),
         np.linspace(0, 1, 100),
         label='baseline',
         linestyle='--')
plt.title('Receiver Operating Characteristic Curve', fontsize=18)
plt.ylabel('TPR', fontsize=16)
plt.xlabel('FPR', fontsize=16)
plt.legend(fontsize=12);

tp, tn, fp, fn = predict_metrics(test_loss_normal, test_loss_anomalies, threshold)
accuracy, precision, recall, f1 = compute_stats(tp, tn, fp, fn)


print("Accuracy = {}".format(np.round(accuracy,4)))
print("Precision = {}".format(np.round(precision,4)))
print("Recall = {}".format(np.round(recall,4)))
print("F1-score = {}".format(np.round(f1,4)))

Accuracy = 0.9552
Precision = 0.9104
Recall = 0.9848
F1-score = 0.9461

工学博士,担任《Mechanical System and Signal Processing》《中国电机工程学报》《控制与决策》等期刊审稿专家,擅长领域:现代信号处理,机器学习,深度学习,数字孪生,时间序列分析,设备缺陷检测、设备异常检测、设备智能故障诊断与健康管理PHM等。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据提供的引用内容,信用卡欺诈检测可以使用LSTM自动编码器进行异常检测LSTM自动编码器是一种无监督学习算法,它使用长短期记忆(LSTM神经网络来生成信用卡交易的低维表示。该算法的基本假设是正常交易和欺诈交易具有不同的分布。 以下是使用LSTM自动编码器进行信用卡欺诈检测的步骤[^1]: 1. 数据预处理:首先,对信用卡交易数据进行预处理,包括缺失值处理、标准化等。确保数据的质量和一致性。 2. 构建LSTM自动编码器:使用LSTM神经网络构建自动编码器模型。自动编码器由编码器和解码器组成。编码器将输入数据压缩为低维表示,解码器将低维表示还原为原始数据。 3. 训练模型:使用正常交易数据训练LSTM自动编码器模型。训练过程中,模型尝试将正常交易数据重构为原始数据,同时最小化重构误差。 4. 重构误差计算:使用训练好的LSTM自动编码器模型对测试数据进行重构。计算每个交易的重构误差,即原始数据与重构数据之间的差异。 5. 设置阈值:根据重构误差的分布情况,设置一个合适的阈值。当重构误差超过阈值时,将该交易标记为异常。 6. 异常检测:对于新的信用卡交易数据,使用训练好的模型进行重构和重构误差计算。根据阈值判断交易是否为异常。 请注意,LSTM自动编码器是一种无监督学习算法,它可以检测出与正常分布不同的异常交易。然而,它可能无法检测到新型的欺诈手法或者与正常交易非常相似的欺诈交易。因此,在实际应用中,需要结合其他的异常检测方法来提高检测的准确性和鲁棒性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哥廷根数学学派

码字不易,且行且珍惜

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值