一个真实感比较强的模拟案例:
设备每秒采集:入口压力、流量、振动。
训练阶段只有正常数据。
系统每收到一个新数据点,就取最近 60 秒数据,交给 AutoEncoder 重构。
如果重构误差超过阈值,就报警。
最终做到:
传感器数据→60秒滑动窗口→标准化→AutoEncoder→重构→计算误差→阈值判断→异常报警 传感器数据 \rightarrow 60秒滑动窗口 \rightarrow 标准化 \rightarrow AutoEncoder \rightarrow 重构 \rightarrow 计算误差 \rightarrow 阈值判断 \rightarrow 异常报警 传感器数据→60秒滑动窗口→标准化→AutoEncoder→重构→计算误差→阈值判断→异常报警
一、先把最终系统想清楚
你最终上线的系统实际上长这样:
压力传感器 ─┐
流量传感器 ─┼──> 实时数据流
振动传感器 ─┘
│
▼
最近60秒数据窗口
│
▼
数据标准化
│
▼
AutoEncoder模型
│
┌─────┴─────┐
│ │
原始数据 重构数据
│ │
└─────┬─────┘
▼
重构误差
│
┌────────┴────────┐
│ │
Error < 阈值 Error > 阈值
│ │
正常 异常
│
▼
产生报警事件
初学者首先牢记:
模型并不知道“轴承故障”“阀门卡滞”是什么。
它只知道:
这个数据看起来不像我学过的正常状态。
因此第一阶段我们做的是:
异常检测 \boxed{异常检测} 异常检测
不是:
故障诊断 \boxed{故障诊断} 故障诊断
后面才能继续做:
异常检测→异常定位→故障诊断 异常检测 \rightarrow 异常定位 \rightarrow 故障诊断 异常检测→异常定位→故障诊断
二、第一步:搭建 Python 环境
建议新建一个目录:
time_series_anomaly/
安装:
pip install numpy pandas matplotlib scikit-learn torch joblib fastapi uvicorn
工程最终可以变成:
time_series_anomaly/
│
├── train.py
├── realtime_detector.py
├── api.py
│
├── ae_model.pt
├── scaler.pkl
└── config.json
现在先不用拆文件。
先写一个:
train.py
把完整训练流程跑起来。
三、第二步:模拟“正常设备数据”
假设设备有三个测点:
pressure 压力 MPa
flow 流量 m³/h
vibration 振动
正常设备不是一条完全直线,而是会正常波动。
import numpy as np
import pandas as pd
np.random.seed(42)
N = 12000
t = np.arange(N)
pressure = (
2.0
+ 0.08 * np.sin(2 * np.pi * t / 120)
+ np.random.normal(0, 0.02, N)
)
flow = (
100
+ 4 * np.sin(2 * np.pi * t / 120 + 0.4)
+ np.random.normal(0, 0.8, N)
)
vibration = (
0.20
+ 0.015 * np.sin(2 * np.pi * t / 40)
+ np.random.normal(0, 0.005, N)
)
df = pd.DataFrame({
"pressure": pressure,
"flow": flow,
"vibration": vibration
})
print(df.head())
你会得到类似:
pressure flow vibration
0 2.0099 101.3 0.204
1 2.0014 101.9 0.202
2 2.0213 101.5 0.206
...
这里的关键点是:
这 12000 个数据全部都是“正常数据”。
它们就是模型的教材。
四、第三步:理解为什么必须标准化
现在三个变量数量级完全不一样:
压力:
2 MPa
流量:
100 m³/h
振动:
0.2
如果直接计算 MSE:
MSE=(X−X^)2 MSE=(X-\hat X)^2 MSE=(X−X^)2
那么流量的误差天然比振动大很多。
模型就会产生一种错觉:
流量最重要。
实际上只是单位不同。
所以必须标准化:
x′=x−μσ x'=\frac{x-\mu}{\sigma} x′=σx−μ
代码:
from sklearn.preprocessing import StandardScaler
FEATURES = [
"pressure",
"flow",
"vibration"
]
split = int(len(df) * 0.7)
train_df = df.iloc[:split]
val_df = df.iloc[split:]
scaler = StandardScaler()
train_scaled = scaler.fit_transform(
train_df[FEATURES]
)
val_scaled = scaler.transform(
val_df[FEATURES]
)
有一个工程细节非常重要:
fit()只能在训练数据上做。
不能这样:
scaler.fit_transform(全部数据)
否则就是:
数据泄漏 \boxed{数据泄漏} 数据泄漏
五、第四步:为什么不能每次只输入一个数据点
如果某时刻:
压力 = 2.10 MPa
单独看它可能没有问题。
但假如最近的数据是:
2.00
2.02
2.06
2.10
2.17
2.25
2.38
那么问题不是:
2.10 是否异常。
而是:
压力正在持续快速上涨。
所以时间序列模型需要看一个窗口。
例如:
Window=60 Window=60 Window=60
也就是一次看最近 60 秒。
t-59
t-58
...
t-2
t-1
t
三个传感器:
60 × 3
因此一个样本实际上有:
60×3=180 60\times3=180 60×3=180
个数字。
六、第五步:构造滑动窗口
写:
WINDOW = 60
def make_windows(data, window=60):
X = []
for i in range(len(data) - window + 1):
block = data[i:i + window]
X.append(
block.reshape(-1)
)
return np.array(X)
然后:
X_train = make_windows(
train_scaled,
WINDOW
)
X_val = make_windows(
val_scaled,
WINDOW
)
print(X_train.shape)
可能输出:
(8341, 180)
也就是说:
8341 个训练样本
每个样本:
180维
七、现在真正理解一下一个训练样本是什么
模型看到的不是:
压力 = 2.01
而是:
最近60秒:
pressure:
2.01
2.02
2.03
2.01
...
flow:
101
102
101
100
...
vibration:
0.201
0.203
0.199
...
最终展开成:
[
p1, f1, v1,
p2, f2, v2,
p3, f3, v3,
...
p60, f60, v60
]
总共:
180个数字
八、第六步:建立最简单的 AutoEncoder
先不要用 Transformer。
先做一个最容易理解、最容易上线的全连接 AE。
import torch
import torch.nn as nn
class AutoEncoder(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, 32),
nn.ReLU(),
nn.Linear(32, 8)
)
self.decoder = nn.Sequential(
nn.Linear(8, 32),
nn.ReLU(),
nn.Linear(32, 128),
nn.ReLU(),
nn.Linear(128, input_dim)
)
def forward(self, x):
z = self.encoder(x)
x_hat = self.decoder(z)
return x_hat
这个网络本质是:
180
↓
128
↓
32
↓
8
↑
32
↑
128
↑
180
最关键的是:
180 → 8
模型被迫把:
60秒 × 3传感器
压缩成:
8个数字
九、为什么要故意压缩?
这是 AutoEncoder 的灵魂。
如果:
180 → 1000 → 1000 → 180
模型能力太强,有可能直接:
把输入复制到输出。
这就失去意义了。
我们故意让它:
180 → 8
相当于告诉模型:
不能死记硬背,你必须找到正常设备真正的核心规律。
比如模型可能隐式学会:
压力大概怎么变化
流量跟压力什么关系
振动应该多大
60秒内趋势怎样变化
十、第七步:训练模型
先转换成 PyTorch Tensor:
X_train_tensor = torch.tensor(
X_train,
dtype=torch.float32
)
X_val_tensor = torch.tensor(
X_val,
dtype=torch.float32
)
创建模型:
input_dim = X_train.shape[1]
model = AutoEncoder(input_dim)
损失函数:
criterion = nn.MSELoss()
优化器:
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001
)
训练:
EPOCHS = 30
BATCH_SIZE = 128
for epoch in range(EPOCHS):
model.train()
permutation = torch.randperm(
X_train_tensor.size(0)
)
total_loss = 0
for i in range(
0,
X_train_tensor.size(0),
BATCH_SIZE
):
indices = permutation[
i:i+BATCH_SIZE
]
batch = X_train_tensor[indices]
optimizer.zero_grad()
reconstructed = model(batch)
loss = criterion(
reconstructed,
batch
)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(
f"Epoch {epoch+1:02d}, "
f"Loss={total_loss:.4f}"
)
这时候发生了什么?
模型每次拿到:
X X X
然后生成:
X^ \hat X X^
训练目标就是:
X≈X^ X\approx\hat X X≈X^
损失:
L=1N∑(X−X^)2 L= \frac1N \sum (X-\hat X)^2 L=N1∑(X−X^)2
不断让:
L↓ L\downarrow L↓
十一、训练完成以后,模型学到了什么?
非常重要。
它并没有学:
故障A
故障B
故障C
它学的是:
正常运行空间 \boxed{\text{正常运行空间}} 正常运行空间
可以把它想象成:
异常 ×
● ● ● ● ●
● ● ● ● ● ● ●
● ● 正常 ● ●
● ● ● ● ● ●
● ● ● ●
× 异常
× 异常
这些:
●
是正常样本。
AutoEncoder学到了这个正常区域。
十二、第八步:计算正常数据的重构误差
训练完成后:
model.eval()
with torch.no_grad():
reconstructed = model(
X_val_tensor
)
errors = torch.mean(
(X_val_tensor - reconstructed) ** 2,
dim=1
)
errors = errors.numpy()
现在:
errors
可能长这样:
0.021
0.019
0.025
0.031
0.018
0.028
...
这些都是:
正常样本重构误差 正常样本重构误差 正常样本重构误差
十三、第九步:确定报警阈值
这是整个异常检测系统里非常关键的一步。
例如取:
threshold = np.quantile(
errors,
0.995
)
print(
"threshold =",
threshold
)
意思是:
正常数据 99.5% 的重构误差都小于这个值。
假设:
threshold = 0.082
那么:
Error <= 0.082
认为正常。
而:
Error > 0.082
认为异常。
即:
Error>τ⇒异常 \boxed{ Error>\tau \Rightarrow 异常 } Error>τ⇒异常
十四、第十步:人为制造几个设备异常
现在进入最有意思的阶段。
我们制造一段测试数据。
N_TEST = 3000
t2 = np.arange(
N,
N + N_TEST
)
pressure_test = (
2.0
+ 0.08 * np.sin(
2*np.pi*t2/120
)
+ np.random.normal(
0,
0.02,
N_TEST
)
)
flow_test = (
100
+ 4*np.sin(
2*np.pi*t2/120 + 0.4
)
+ np.random.normal(
0,
0.8,
N_TEST
)
)
vibration_test = (
0.20
+ 0.015*np.sin(
2*np.pi*t2/40
)
+ np.random.normal(
0,
0.005,
N_TEST
)
)
现在人为制造三个异常。
异常1:压力突然升高
pressure_test[
700:760
] += 0.6
相当于:
正常:
2.01
2.03
2.04
突然:
2.58
2.61
2.65
十五、异常2:振动突然变大
vibration_test[
1400:1500
] += 0.15
正常:
0.20
异常:
0.35
十六、异常3:压力慢慢漂移
这个更加符合工业现场。
pressure_test[
2100:2300
] += np.linspace(
0,
0.5,
200
)
开始:
2.00
逐渐:
2.05
2.10
2.20
2.30
2.40
2.50
这种异常传统固定阈值很可能发现得晚。
而时序模型可能提前发现:
趋势已经不像正常运行了。
十七、组合测试数据
test_df = pd.DataFrame({
"pressure": pressure_test,
"flow": flow_test,
"vibration": vibration_test
})
注意:
这里不能重新:
fit_transform
必须:
test_scaled = scaler.transform(
test_df[FEATURES]
)
原因:
上线以后必须保持训练阶段的数据尺度。
十八、生成测试窗口
X_test = make_windows(
test_scaled,
WINDOW
)
X_test_tensor = torch.tensor(
X_test,
dtype=torch.float32
)
十九、计算测试异常分数
model.eval()
with torch.no_grad():
reconstructed_test = model(
X_test_tensor
)
test_errors = torch.mean(
(
X_test_tensor
- reconstructed_test
) ** 2,
dim=1
).numpy()
现在:
test_errors
就是每个时间窗口的:
异常分数 \boxed{异常分数} 异常分数
二十、真正产生报警
anomaly_flags = (
test_errors > threshold
)
例如:
时间 Error 状态
650 0.028 正常
651 0.031 正常
652 0.025 正常
705 0.16 异常
706 0.28 异常
707 0.41 异常
...
这就是第一版工业异常检测。
二十一、把异常分数画出来
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 5))
plt.plot(
test_errors,
label="Reconstruction Error"
)
plt.axhline(
threshold,
linestyle="--",
label="Threshold"
)
plt.xlabel("Time Window")
plt.ylabel("Reconstruction Error")
plt.legend()
plt.show()
你应该看到:
Error
│
1 │ /\ /\
│ / \ / \
│
τ │------阈值--------------------------------
│
│____正常_______/________正常___/__________
└────────────────────────────────── 时间
这时候初学者通常就真正理解了:
系统根本没有训练“压力异常”。
但压力异常出现以后:
X X X
和:
X^ \hat X X^
差异明显变大。
于是:
Error↑ Error\uparrow Error↑
二十二、进一步看“模型到底哪里重构错了”
这一步对于理解特别重要。
取一个异常窗口:
index = 720
original = X_test[index]
with torch.no_grad():
reconstructed = model(
torch.tensor(
original,
dtype=torch.float32
).unsqueeze(0)
).numpy()[0]
恢复为:
60 × 3
original = original.reshape(
WINDOW,
3
)
reconstructed = reconstructed.reshape(
WINDOW,
3
)
例如画压力:
plt.figure(figsize=(12, 4))
plt.plot(
original[:, 0],
label="Real Pressure"
)
plt.plot(
reconstructed[:, 0],
label="Reconstructed Pressure"
)
plt.legend()
plt.show()
正常情况下你会看到:
真实 ~~~~~~~
重构 ~~~~~~~
几乎重合。
异常情况下:
真实 /\
/ \
/ \
重构 ~~~~~~~~~~~~~
模型相当于在说:
“按照我的正常经验,这里本来不应该突然冲这么高。”
二十三、到这里你已经完成了离线异常检测
现在完整流程已经是:
历史正常数据
↓
数据清洗
↓
标准化
↓
滑动窗口
↓
AutoEncoder训练
↓
正常误差分布
↓
确定阈值
↓
保存模型
测试:
新数据
↓
滑动窗口
↓
标准化
↓
AutoEncoder
↓
重构误差
↓
阈值比较
↓
正常 / 异常
下面开始真正工程化。
二十四、第十一步:保存模型
训练完成:
import torch
import joblib
import json
torch.save(
{
"model_state_dict":
model.state_dict(),
"input_dim":
input_dim,
"window":
WINDOW,
"features":
FEATURES
},
"ae_model.pt"
)
保存标准化模型:
joblib.dump(
scaler,
"scaler.pkl"
)
保存阈值:
with open(
"config.json",
"w",
encoding="utf-8"
) as f:
json.dump(
{
"threshold":
float(threshold),
"window":
WINDOW
},
f,
indent=2
)
现在你的工程中有:
ae_model.pt
scaler.pkl
config.json
二十五、第十二步:做真正的实时检测
现场不是:
一次给你 3000 条数据。
而是:
10:01:01 来1条
10:01:02 来1条
10:01:03 来1条
...
所以需要一个缓冲区:
最近60条数据
Python可以用:
deque
二十六、实时检测器
创建:
realtime_detector.py
代码:
from collections import deque
import json
import joblib
import numpy as np
import torch
import torch.nn as nn
class AutoEncoder(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, 32),
nn.ReLU(),
nn.Linear(32, 8)
)
self.decoder = nn.Sequential(
nn.Linear(8, 32),
nn.ReLU(),
nn.Linear(32, 128),
nn.ReLU(),
nn.Linear(128, input_dim)
)
def forward(self, x):
z = self.encoder(x)
return self.decoder(z)
class RealtimeDetector:
def __init__(self):
checkpoint = torch.load(
"ae_model.pt",
map_location="cpu"
)
self.window = checkpoint["window"]
self.features = checkpoint["features"]
self.scaler = joblib.load(
"scaler.pkl"
)
with open(
"config.json",
"r",
encoding="utf-8"
) as f:
config = json.load(f)
self.threshold = config["threshold"]
self.model = AutoEncoder(
checkpoint["input_dim"]
)
self.model.load_state_dict(
checkpoint["model_state_dict"]
)
self.model.eval()
self.buffer = deque(
maxlen=self.window
)
def add_point(
self,
pressure,
flow,
vibration
):
row = [
pressure,
flow,
vibration
]
self.buffer.append(row)
if len(self.buffer) < self.window:
return {
"ready": False,
"message":
f"正在积累数据 "
f"{len(self.buffer)}/{self.window}"
}
data = np.array(
self.buffer
)
data_scaled = self.scaler.transform(
data
)
x = data_scaled.reshape(
1,
-1
)
x_tensor = torch.tensor(
x,
dtype=torch.float32
)
with torch.no_grad():
x_hat = self.model(
x_tensor
)
error = torch.mean(
(
x_tensor
- x_hat
) ** 2
).item()
is_anomaly = (
error > self.threshold
)
return {
"ready": True,
"score": error,
"threshold": self.threshold,
"is_anomaly": is_anomaly
}
二十七、模拟设备实时发送数据
写:
from realtime_detector import RealtimeDetector
detector = RealtimeDetector()
for i, row in test_df.iterrows():
result = detector.add_point(
row["pressure"],
row["flow"],
row["vibration"]
)
if (
result["ready"]
and result["is_anomaly"]
):
print(
"异常报警",
i,
result
)
这样就是:
传感器
↓
实时进入
↓
60秒缓存
↓
AI检测
↓
报警
二十八、但是工业现场绝对不能“Error一超阈值就报警”
这是从学生实验走向工程系统最重要的一步。
因为偶尔一个噪声:
Error > threshold
很正常。
如果直接报警:
滴滴滴
滴滴滴
滴滴滴
现场人员几天之后就把系统关掉了。
所以应该增加:
持续性判断 \boxed{持续性判断} 持续性判断
例如:
最近5次里面至少3次异常,才产生正式报警。
即:
0 0 1 0 0
不报警。
但:
1 0 1 1 1
报警。
二十九、增加“3/5报警机制”
可以增加:
self.score_history = deque(
maxlen=5
)
每次:
self.score_history.append(
error > self.threshold
)
然后:
alarm = (
sum(self.score_history) >= 3
)
这样误报警会明显下降。
三十、更工程化:设置四级异常
不要只做:
正常 / 异常
可以变成:
正常
关注
预警
报警
比如:
S=ErrorThreshold S=\frac{Error}{Threshold} S=ThresholdError
定义:
S < 0.8 正常
0.8~1.0 关注
1.0~2.0 黄色预警
>2.0 红色报警
代码:
ratio = error / self.threshold
if ratio < 0.8:
level = "NORMAL"
elif ratio < 1.0:
level = "WATCH"
elif ratio < 2.0:
level = "WARNING"
else:
level = "ALARM"
这个就已经非常接近工业系统了。
三十一、下一步:解决“到底哪个变量异常”
这是非常重要的一步。
如果系统只告诉操作员:
AI异常分数 = 0.46
操作员会说:
所以呢?
必须告诉他:
压力异常
还是
流量异常
还是
振动异常
可以计算每个变量自己的重构误差:
Ej=1T∑t(xt,j−x^t,j)2 E_j= \frac1T \sum_t (x_{t,j}-\hat x_{t,j})^2 Ej=T1t∑(xt,j−x^t,j)2
代码:
real = x_tensor.numpy().reshape(
self.window,
3
)
recon = x_hat.numpy().reshape(
self.window,
3
)
feature_errors = np.mean(
(real - recon) ** 2,
axis=0
)
得到:
pressure_error = 0.52
flow_error = 0.03
vibration_error = 0.02
那么就可以告诉用户:
主要异常变量:
1. 压力
2. 流量
3. 振动
三十二、系统输出应该从这样
不要:
{
"score": 0.63
}
而应该输出:
{
"device_id": "REG-001",
"status": "WARNING",
"score": 0.63,
"threshold": 0.12,
"main_abnormal_feature": "pressure",
"feature_errors": {
"pressure": 0.51,
"flow": 0.07,
"vibration": 0.05
}
}
这才有工程意义。
三十三、第十三步:把模型变成 API
现场的 SCADA、WinForms、Java、C# 系统通常不会直接运行 Python 模型。
最好做成:
HTTPAPI HTTP API HTTPAPI
例如:
POST /detect
创建:
api.py
三十四、FastAPI版本
from fastapi import FastAPI
from pydantic import BaseModel
from realtime_detector import (
RealtimeDetector
)
app = FastAPI(
title="Time Series Anomaly API"
)
detector = RealtimeDetector()
class SensorData(BaseModel):
pressure: float
flow: float
vibration: float
@app.post("/detect")
def detect(data: SensorData):
result = detector.add_point(
pressure=data.pressure,
flow=data.flow,
vibration=data.vibration
)
return result
启动:
uvicorn api:app \
--host 0.0.0.0 \
--port 8000
浏览器打开:
http://localhost:8000/docs
你会看到自动生成的 API 页面。
三十五、现场系统怎么调用
例如发送:
{
"pressure": 2.03,
"flow": 101.2,
"vibration": 0.203
}
API返回:
{
"ready": true,
"score": 0.031,
"threshold": 0.086,
"is_anomaly": false
}
异常时:
{
"ready": true,
"score": 0.537,
"threshold": 0.086,
"is_anomaly": true
}
这样:
PLC
SCADA
WinForms
Web
MES
边缘网关
都可以调用。
三十六、真正上线以后不能这样设计
现在这个:
detector = RealtimeDetector()
只有:
一个buffer
所以只适合:
一台设备
真实现场可能有:
REG-001
REG-002
REG-003
...
每台设备都必须维护自己的:
60秒窗口
结构应该变成:
设备ID
│
├── REG001 → Buffer
│
├── REG002 → Buffer
│
└── REG003 → Buffer
代码思想:
detectors = {}
def get_detector(device_id):
if device_id not in detectors:
detectors[device_id] = (
RealtimeDetector()
)
return detectors[device_id]
这就变成多设备了。
三十七、真正生产系统建议这样设计
最终架构:
┌─────────────────────┐
│ PLC / RTU / SCADA │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 数据采集层 │
│ OPC UA / MQTT / HTTP │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 实时数据缓存 │
│ Redis / 内存 / Kafka │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 异常检测服务 │
│ AutoEncoder │
│ Reconstruction Error│
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 报警规则引擎 │
│ 阈值 + 持续时间 │
│ 3/5 + 抑制 + 去抖 │
└──────────┬──────────┘
│
┌─────┴─────┐
▼ ▼
SCADA Web平台
报警 风险看板
三十八、上线时一定要保存5样东西
很多论文模型不能上线,就是漏掉这些。
必须保存:
1. 模型
ae_model.pt
2. 标准化器
scaler.pkl
3. 阈值
threshold
4. 特征顺序
必须固定:
pressure
flow
vibration
绝对不能训练是:
pressure, flow, vibration
上线变成:
flow, pressure, vibration
模型会直接废掉。
5. 窗口长度
例如:
60
训练和上线必须一致。
三十九、建议再保存一个完整配置文件
例如:
{
"model_version": "1.0.0",
"window_size": 60,
"sample_interval_sec": 1,
"features": [
"pressure",
"flow",
"vibration"
],
"threshold": 0.086,
"alarm_rule": {
"window": 5,
"min_abnormal_count": 3
}
}
这非常重要。
因为半年以后你自己都会忘:
当时到底怎么训练的。
四十、真正工业数据还必须做数据质量判断
在送给 AI 之前一定要判断:
有没有空值?
有没有断线?
有没有恒定值?
有没有明显超量程?
有没有时间戳乱序?
例如:
if pressure is None:
return "DATA_INVALID"
如果传感器压力量程:
0~10MPa
收到:
99999MPa
不要让AI判断。
应该直接:
传感器数据质量异常
所以真正系统是:
数据质量检测→AI异常检测 数据质量检测 \rightarrow AI异常检测 数据质量检测→AI异常检测
而不是直接AI。
四十一、工业现场推荐“四层判断”
如果你以后做燃气调压器、泵、压缩机,我建议采用:
第一层:数据质量
第二层:传统安全阈值
第三层:AI异常检测
第四层:故障诊断
比如:
压力 > 安全上限
这种事情千万不要等 AI。
直接报警。
AI主要负责发现:
还没越限,但是运行模式已经不正常。
这才是AI真正有价值的地方。
四十二、举一个现场案例
正常情况:
入口压力 4.0 MPa
出口压力 2.0 MPa
流量 100
振动 0.20
某天:
入口压力 正常
出口压力 2.05
流量 正常
振动 0.23
所有数据都:
没有超过传统阈值
所以SCADA:
不报警
但最近60分钟模式开始变化:
出口压力波动越来越大
振动逐渐增加
压力和流量关系开始改变
AE发现:
Error:0.02→0.03→0.05→0.09→0.16 Error: 0.02 \rightarrow 0.03 \rightarrow 0.05 \rightarrow 0.09 \rightarrow 0.16 Error:0.02→0.03→0.05→0.09→0.16
如果阈值:
τ=0.08 \tau=0.08 τ=0.08
系统在:
0.09
就开始预警。
这就是:
从“超限报警”变成“模式异常预警”。
四十三、这才是你的系统真正应该显示的界面
建议以后界面不要只显示一个AI分数。
应该有:
设备:调压器 REG-001
状态:黄色预警
当前异常分数:
0.126
正常阈值:
0.086
异常程度:
1.47 × threshold
主要异常变量:
1. 出口压力 58%
2. 振动 27%
3. 流量 15%
最近趋势:
持续升高 ↑
持续时间:
6 min
建议:
关注出口压力波动及调压机构状态
这样现场人员才能真正使用。
四十四、从实验代码到真正上线,我建议分成6级
Level 1
CSV
+
AE
+
重构误差
证明算法可行。
Level 2
真实历史数据
+
滑动窗口
+
正常训练
验证真实设备。
Level 3
增加:
变量级异常贡献
知道哪个传感器异常。
Level 4
增加:
持续时间
3/5策略
去抖
报警抑制
解决现场误报警。
Level 5
接:
OPC UA / MQTT / SCADA
做到实时运行。
Level 6
增加:
故障知识库
+
规则
+
机理
+
AI异常检测
最终变成:
智能故障诊断系统 \boxed{智能故障诊断系统} 智能故障诊断系统
四十五、一定要理解一个非常重要的问题
AutoEncoder最适合做:
“你现在不像正常状态。”
它不是天然擅长:
“你一定是阀杆卡涩。”
例如:
Error = 0.58
只能可靠说明:
异常 \boxed{异常} 异常
然后进一步看:
压力贡献最大
振动次之
再结合规则:
出口压力持续振荡
+
振动升高
+
入口压力稳定
才可能判断:
疑似调压机构振荡/卡涩
所以真正高级的故障诊断系统是:
AI异常检测+变量定位+规则+机理+故障知识 \boxed{ AI异常检测 + 变量定位 + 规则 + 机理 + 故障知识 } AI异常检测+变量定位+规则+机理+故障知识
而不是一个 AE 包打天下。
四十六、给初学者一个最值得记住的完整逻辑
你可以把整个系统想成一个只接受过“健康体检”的医生。
第一步
让医生天天看:
健康人的心电图
第二步
医生逐渐形成:
健康应该长什么样
第三步
来了一个新病人。
医生尝试按照自己的“健康规律”还原。
第四步
如果:
真实 ≈ 健康模型重构
说明:
正常
第五步
如果:
真实 ≠ 重构
说明:
偏离正常
最终就是:
正常数据→学习正常规律→实时数据→重构→误差→异常分数→持续性判断→报警 \boxed{ 正常数据 \rightarrow 学习正常规律 \rightarrow 实时数据 \rightarrow 重构 \rightarrow 误差 \rightarrow 异常分数 \rightarrow 持续性判断 \rightarrow 报警 } 正常数据→学习正常规律→实时数据→重构→误差→异常分数→持续性判断→报警
如果以你前面一直研究的燃气调压器、泵、压缩机等工业设备故障诊断为目标,下一步最值得做的,不是马上换 Transformer,而是把这个示例继续升级成一个真正的:
压力+振动+声纹→实时AutoEncoder异常检测→异常变量定位→故障诊断→FastAPI→WinForms/SCADA调用 \boxed{ 压力+振动+声纹 \rightarrow 实时AutoEncoder异常检测 \rightarrow 异常变量定位 \rightarrow 故障诊断 \rightarrow FastAPI \rightarrow WinForms/SCADA调用 } 压力+振动+声纹→实时AutoEncoder异常检测→异常变量定位→故障诊断→FastAPI→WinForms/SCADA调用
的完整工程。这样你会把“论文里的重构异常检测”和“现场真正能跑的软件”一次彻底打通。

419

被折叠的 条评论
为什么被折叠?



