
文章目录
-
- [1. 课前导读](#1. 课前导读)
-
- [1.1 本节课学习目标](#1.1 本节课学习目标)
- [1.2 知识重难点](#1.2 知识重难点)
- [1.3 学习前置条件](#1.3 学习前置条件)
- [1.4 学完可掌握能力](#1.4 学完可掌握能力)
- [1.5 行业应用场景](#1.5 行业应用场景)
- [2. 核心理论精讲](#2. 核心理论精讲)
-
- [2.1 异常检测问题定义](#2.1 异常检测问题定义)
- [2.2 统计方法](#2.2 统计方法)
- [2.3 孤立森林(Isolation Forest)](#2.3 孤立森林(Isolation Forest))
- [2.4 自编码器重构误差](#2.4 自编码器重构误差)
- [2.5 LSTM预测误差方法](#2.5 LSTM预测误差方法)
- [2.6 阈值选择](#2.6 阈值选择)
- [2.7 评估指标(不平衡数据)](#2.7 评估指标(不平衡数据))
- [3. 环境搭建与工具配置](#3. 环境搭建与工具配置)
- [4. 代码实战教学](#4. 代码实战教学)
-
- [4.1 生成模拟工业传感器数据](#4.1 生成模拟工业传感器数据)
- [4.2 自编码器模型构建](#4.2 自编码器模型构建)
- [4.3 计算重构误差并选择阈值](#4.3 计算重构误差并选择阈值)
- [4.4 基于LSTM的时序预测误差方法](#4.4 基于LSTM的时序预测误差方法)
- [4.5 孤立森林对比](#4.5 孤立森林对比)
- [4.6 结果可视化](#4.6 结果可视化)
- [5. 案例实操演练](#5. 案例实操演练)
-
- [5.1 下载与加载数据](#5.1 下载与加载数据)
- [5.2 特征工程](#5.2 特征工程)
- [5.3 训练自编码器](#5.3 训练自编码器)
- [5.4 结果分析](#5.4 结果分析)
- [6. 常见坑点与排错总结](#6. 常见坑点与排错总结)
-
- [6.1 数据预处理坑点](#6.1 数据预处理坑点)
- [6.2 自编码器训练坑点](#6.2 自编码器训练坑点)
- [6.3 LSTM预测方法坑点](#6.3 LSTM预测方法坑点)
- [6.4 评估坑点](#6.4 评估坑点)
- [7. 知识点总结 + 课后作业](#7. 知识点总结 + 课后作业)
-
- [7.1 核心知识点梳理](#7.1 核心知识点梳理)
- [7.2 基础作业](#7.2 基础作业)
- [7.3 进阶实操作业](#7.3 进阶实操作业)
- [7.4 思考拓展题](#7.4 思考拓展题)
- [🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航](#🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航)
1. 课前导读
1.1 本节课学习目标
- 理解异常检测的任务类型:点异常、上下文异常、集合异常,以及无监督/监督/半监督的区别。
- 掌握基于统计的方法(Z-score、IQR)及其局限性。
- 掌握孤立森林(Isolation Forest)的原理及其在异常检测中的应用。
- 深入理解基于自编码器(Autoencoder)的重构误差异常检测方法,能够使用TensorFlow搭建。
- 掌握基于LSTM的时序预测误差方法,用于传感器时间序列异常检测。
- 学会确定异常阈值(百分位数、KDE、3σ)。
- 能够评估异常检测模型(混淆矩阵、F1、AUC),并处理类别不平衡。
- 完成工业设备故障识别的完整实战项目。
1.2 知识重难点
| 类别 | 内容 |
|---|---|
| 重点 | 自编码器重构误差的计算与阈值选择;LSTM预测误差方法;评估指标在不平衡数据下的含义 |
| 难点 | 异常阈值选择的统计学原理;自编码器对正常模式的学习能力与过拟合控制;时序数据的滑动窗口构造 |
| 易混淆点 | 重构误差 vs 预测误差;无监督与半监督异常检测的标签使用(半监督使用少量标注异常);异常得分与二值化阈值的确定 |
1.3 学习前置条件
- 已掌握TensorFlow基础,能够搭建全连接网络和LSTM(第15、26课)。
- 了解无监督学习基本概念。
- 具备一定的统计学知识(均值、方差、分位数)。
1.4 学完可掌握能力
- 独立构建基于自编码器的异常检测系统,用于非时序数据。
- 构建基于LSTM的时序异常检测模型,用于传感器数据。
- 处理不平衡数据下的模型评估。
- 将模型部署为实时API或批处理任务。
1.5 行业应用场景
- 设备预测性维护:提前发现轴承、电机异常。
- 金融反欺诈:识别异常交易。
- 网络安全:检测入侵行为。
- 产品质量检测:识别缺陷产品。
2. 核心理论精讲
2.1 异常检测问题定义
异常是指与其他数据分布显著不同的样本。异常检测的目标是识别这些少数样本。根据标签可用性分为:
- 无监督异常检测:训练集无标签,假设异常样本极少且与正常样本分布不同。
- 监督异常检测:有标注的正常和异常样本,但类别极不平衡。
- 半监督异常检测:训练集仅包含正常样本,学习正常模式,测试时检测偏离的样本。
工业场景通常为半监督:我们有大量正常设备运行数据,故障数据稀缺。因此本课重点采用半监督方法。
2.2 统计方法
Z-score:假设特征服从正态分布,计算样本偏离均值的标准差倍数,超过阈值(如3)视为异常。但实际工业数据往往不满足正态分布。
IQR(四分位距):异常值定义为小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR。适用于单变量。
局限性:无法处理复杂高维相关性。
2.3 孤立森林(Isolation Forest)
通过随机选择特征和切分值递归划分数据,异常点更容易被孤立(路径长度短)。集成模型,对高维数据有效,且无需假设数据分布。可用sklearn实现。
2.4 自编码器重构误差
自编码器(Autoencoder)是一种无监督神经网络,由编码器和解码器组成,训练目标是重构输入(输入=输出)。如果仅用正常数据训练,自编码器将学习到正常模式的重构。当输入异常样本时,重构误差(如MSE)会显著增大。因此,重构误差可作为异常分数。
训练 :最小化正常样本的重构损失。
推断:计算每个样本的重构误差,若超过阈值则判定为异常。
网络结构:可以是对称的全连接网络或卷积网络(图像)、LSTM(时序)。
2.5 LSTM预测误差方法
对于时序数据,可以用LSTM预测下一个时间步的值,然后计算预测值与真实值的误差。同样,模型只在正常数据上训练,异常数据会导致预测误差增大。
滑动窗口:使用过去L个时间步预测未来h步(通常h=1)。将预测误差(如绝对值)作为异常分数。
2.6 阈值选择
异常分数阈值决定分类边界。常用方法:
- 百分位数法:选定一个百分位数p(如99%),将训练集正常数据的重构误差第p百分位数作为阈值。
- 3σ原则:假设误差服从正态分布,阈值 = 均值 + 3×标准差。
- KDE(核密度估计):拟合误差分布,取概率密度低点。
- 基于验证集的优化:如果有一小部分标注异常,可在验证集上最大化F1选择阈值。
2.7 评估指标(不平衡数据)
由于异常样本极少,准确率可能虚高(如全预测正常也有99%准确率)。应使用:
- 精确率(Precision):预测为异常中真正异常的比例。
- 召回率(Recall):真正异常中被检出的比例。
- F1分数:调和平均。
- ROC-AUC:不受阈值影响,评估排序能力。
- PR-AUC:对不平衡数据更敏感。
3. 环境搭建与工具配置
沿用第44课环境,安装scikit-learn(已安装),以及数据处理库。
bash
conda activate tf213
pip install scikit-learn pandas matplotlib
准备模拟工业数据集(或使用真实数据集如CWRU、NAB)。这里生成模拟传感器数据。
项目结构:
anomaly_detection/
├── data/
├── models/
├── train_autoencoder.py
├── train_lstm.py
└── evaluate.py
导入:
python
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix
from tensorflow.keras import layers, models, callbacks
import random
4. 代码实战教学
4.1 生成模拟工业传感器数据
生成正常数据和异常数据。正常数据:多变量时间序列,具有周期性模式。异常数据:在某些时间点出现大幅偏离。
python
def generate_normal_data(n_samples=10000, n_features=4, noise=0.1):
t = np.linspace(0, 100, n_samples)
data = np.zeros((n_samples, n_features))
for i in range(n_features):
# 正弦波 + 趋势
data[:, i] = 2 * np.sin(0.2 * t + i) + 0.01 * t + noise * np.random.randn(n_samples)
return data
def generate_anomaly_data(normal_data, anomaly_ratio=0.05, magnitude=5):
n_samples = normal_data.shape[0]
n_anomaly = int(n_samples * anomaly_ratio)
anomaly_indices = np.random.choice(n_samples, n_anomaly, replace=False)
data = normal_data.copy()
for idx in anomaly_indices:
# 对随机特征添加偏移
feat = np.random.randint(0, data.shape[1])
data[idx, feat] += magnitude * np.random.randn() * data.std(axis=0)[feat]
return data, anomaly_indices
normal = generate_normal_data(10000, 4)
data, anomaly_idx = generate_anomaly_data(normal, anomaly_ratio=0.05)
labels = np.zeros(len(data))
labels[anomaly_idx] = 1
# 划分训练集(仅正常),测试集(含异常)
train_data = normal[:8000] # 仅正常样本
test_data = data[8000:]
test_labels = labels[8000:]
print(f"Train normal samples: {train_data.shape[0]}, Test samples: {test_data.shape[0]}, anomalies: {test_labels.sum()}")
4.2 自编码器模型构建
python
def build_autoencoder(input_dim, encoding_dim=32):
input_layer = layers.Input(shape=(input_dim,))
# 编码器
encoded = layers.Dense(64, activation='relu')(input_layer)
encoded = layers.Dense(encoding_dim, activation='relu')(encoded)
# 解码器
decoded = layers.Dense(64, activation='relu')(encoded)
decoded = layers.Dense(input_dim, activation='linear')(decoded)
autoencoder = models.Model(input_layer, decoded)
autoencoder.compile(optimizer='adam', loss='mse')
return autoencoder
input_dim = train_data.shape[1]
autoencoder = build_autoencoder(input_dim, encoding_dim=8)
autoencoder.summary()
history = autoencoder.fit(train_data, train_data, epochs=50, batch_size=128,
validation_split=0.1, verbose=1, callbacks=[callbacks.EarlyStopping(patience=5)])
4.3 计算重构误差并选择阈值
python
# 在训练集上计算重构误差(用于确定阈值)
train_reconst = autoencoder.predict(train_data)
train_mse = np.mean(np.square(train_data - train_reconst), axis=1)
# 选择99%分位数作为阈值
threshold = np.percentile(train_mse, 99)
print(f"Threshold (99th percentile): {threshold:.4f}")
# 测试集上评估
test_reconst = autoencoder.predict(test_data)
test_mse = np.mean(np.square(test_data - test_reconst), axis=1)
test_pred = (test_mse > threshold).astype(int)
print(classification_report(test_labels, test_pred, target_names=['Normal', 'Anomaly']))
print("AUC:", roc_auc_score(test_labels, test_mse))
4.4 基于LSTM的时序预测误差方法
对于时间序列,构造滑动窗口。
python
def create_sequences(data, window_size=50, pred_len=1):
X, y = [], []
for i in range(len(data) - window_size - pred_len + 1):
X.append(data[i:i+window_size])
y.append(data[i+window_size:i+window_size+pred_len])
return np.array(X), np.array(y)
window = 50
# 将正常数据转换为序列
X_train_seq, y_train_seq = create_sequences(normal, window, 1)
# 训练LSTM预测模型
def build_lstm_predictor(input_shape, lstm_units=64):
model = models.Sequential([
layers.LSTM(lstm_units, activation='relu', input_shape=input_shape, return_sequences=False),
layers.Dense(input_shape[-1])
])
model.compile(optimizer='adam', loss='mse')
return model
lstm_model = build_lstm_predictor((window, normal.shape[1]))
lstm_model.summary()
lstm_model.fit(X_train_seq, y_train_seq, epochs=30, batch_size=64, validation_split=0.1, verbose=1)
# 在测试集上计算预测误差
X_test_seq, y_test_seq = create_sequences(test_data, window, 1)
pred_test = lstm_model.predict(X_test_seq)
test_errors = np.mean(np.square(pred_test - y_test_seq), axis=(1,2)) # 每个样本的MSE
# 阈值同样取训练集误差的99分位数
train_seq_pred = lstm_model.predict(X_train_seq)
train_errors = np.mean(np.square(train_seq_pred - y_train_seq), axis=(1,2))
thresh_lstm = np.percentile(train_errors, 99)
test_pred_lstm = (test_errors > thresh_lstm).astype(int)
# 注意:序列方式预测得到的标签需要对齐原始测试集的标签(略去前window个点)
test_labels_aligned = test_labels[window+1:] # 因为create_sequences会截掉末尾
print(classification_report(test_labels_aligned, test_pred_lstm, target_names=['Normal', 'Anomaly']))
4.5 孤立森林对比
python
from sklearn.ensemble import IsolationForest
iso_forest = IsolationForest(contamination=0.05, random_state=42)
iso_forest.fit(train_data) # 训练时使用正常数据?注意IsolationForest是无监督,可混合异常但比例低,这里仅用正常
pred_iso = iso_forest.predict(test_data)
pred_iso = (pred_iso == -1).astype(int)
print(classification_report(test_labels, pred_iso))
4.6 结果可视化
python
plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plt.plot(test_mse, label='Reconstruction error (AE)')
plt.axhline(threshold, color='r', linestyle='--', label='Threshold')
plt.xlabel('Sample')
plt.ylabel('MSE')
plt.legend()
plt.title('Autoencoder Anomaly Scores')
plt.subplot(1,2,2)
plt.plot(test_errors, label='Prediction error (LSTM)')
plt.axhline(thresh_lstm, color='r', linestyle='--', label='Threshold')
plt.xlabel('Sample')
plt.ylabel('MSE')
plt.legend()
plt.title('LSTM Prediction Error')
plt.show()
5. 案例实操演练
案例:使用CWRU轴承数据集进行故障检测
5.1 下载与加载数据
CWRU数据集包含正常轴承和故障轴承的振动信号。这里模拟其结构。
python
# 假设已经下载了.mat文件,可以使用scipy.io.loadmat
# 为演示,我们继续使用模拟数据,但增加多尺度特征
5.2 特征工程
从原始振动信号提取统计特征:均值、方差、峰值、峰峰值、均方根、偏度、峭度等。将每个时间窗口转换为特征向量。
python
def extract_features(window):
features = []
features.append(np.mean(window))
features.append(np.std(window))
features.append(np.max(window) - np.min(window))
features.append(np.sqrt(np.mean(window**2)))
# 更多特征...
return np.array(features)
# 假设有原始信号列表,滑动窗口提取特征
5.3 训练自编码器
使用正常特征训练,在测试集上检测故障。
5.4 结果分析
输出混淆矩阵,并可视化故障样本的重构误差。
6. 常见坑点与排错总结
6.1 数据预处理坑点
-
坑1:未对数据进行归一化,导致重构误差被量纲大的特征主导。
- 解决 :使用
StandardScaler或MinMaxScaler。
- 解决 :使用
-
坑2:时序数据构造滑动窗口时,应确保训练集和测试集的窗口不重叠(按时间顺序划分)。
6.2 自编码器训练坑点
-
坑3:自编码器过于强大,能够完美重构异常(过拟合正常样本中的噪声)。
- 解决:限制编码器维度(bottleneck),添加正则化(dropout、L2)。
-
坑4:阈值选择过高导致漏报(召回率低),过低导致误报(精确率低)。
- 解决:如果有一小部分异常标签,可使用验证集优化阈值。
6.3 LSTM预测方法坑点
-
坑5:LSTM输入序列长度选择不当,过短无法捕捉周期,过长导致训练慢。
- 经验:根据数据的周期长度选择(如振动信号一个周期点数)。
-
坑6:预测误差的分布可能不服从正态分布,使用3σ可能不合适。
- 替代:使用分位数或核密度估计。
6.4 评估坑点
-
坑7:使用准确率评估不平衡数据,即使全预测正常也会很高。
- 正确:查看精确率、召回率、F1、AUC。
-
坑8:混淆矩阵中的"异常"类预测为正常的代价可能很高(漏报),需根据业务调整阈值。
7. 知识点总结 + 课后作业
7.1 核心知识点梳理
- 异常检测类型:点异常、上下文异常;无监督/监督/半监督。
- 方法:统计方法(Z-score、IQR)、孤立森林、自编码器重构误差、LSTM预测误差。
- 阈值选择:百分位数、3σ、KDE、基于验证集优化。
- 评估:精确率、召回率、F1、AUC(不平衡数据下重点使用)。
7.2 基础作业
- 使用自编码器对模拟的10维数据集进行异常检测,调整编码维度观察性能变化。
- 在工业传感器数据(如来自Kaggle的轴承数据集)上实现LSTM预测误差方法,并报告AUC。
- 比较孤立森林与自编码器在异常检测任务上的F1分数。
7.3 进阶实操作业
任务:基于自编码器集成或多尺度自编码器的异常检测
- 构建多个不同架构的自编码器(如不同编码维度、不同层数),将它们的重构误差求平均作为异常分数。
- 与单一自编码器对比性能。
- 实现一种动态阈值方法(如移动平均)以适应概念漂移。
7.4 思考拓展题
-
如果异常数据与正常数据的分布有部分重叠,如何提高检测率同时降低误报?
-
在实时流式异常检测中,模型需要增量更新。如何设计在线自编码器更新策略?
-
除了重构误差,自编码器还可以利用隐层表示(潜在向量)的密度估计进行异常检测。请设计这样的方案并说明优缺点。
下一课预告:多输入多输出复杂模型设计------我们将学习如何构建具有多个输入(图像、文本、数值)和多个输出(分类、回归)的复杂模型,并处理异构数据。
🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航
第一部分:基础入门(1-10 课)
第二部分:神经网络核心(11-25 课)
第三部分:进阶网络与框架高阶(26-40 课)
第四部分:企业实战与项目落地(41-50 课)
🌟 感谢您耐心阅读到这里!💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~