十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多变量时序异常检测实战:TS2I与GAF图像转换方法详解

多变量时序异常检测实战:TS2I与GAF图像转换方法详解 在工业 AI 场景中有一类问题一直让算法工程师头疼设备传感器每小时产生几十路时序数据每路看起来都正常但整体行为已经偏离基线或者某个异常工况只持续几秒却被淹没在大量噪声里。传统阈值规则误报率高得吓人而纯时序模型又很难同时捕捉多变量之间的耦合关系。这正是 PRISM 这类时序转图像Time Series to Image简称 TS2I方法切入的核心。这篇文章先分析多变量异常检测为什么难再拆解 TS2I 思路在做什么、改变了什么然后给出可落地的 Python 示例和工业部署建议。如果你正在做设备监控、运维指标分析或任何多传感器异常检测项目这篇文章能帮你少踩几个坑。1. 多变量时序异常检测到底难在哪里先看一个具体场景。一条自动化产线上有几十个振动传感器、温度传感器、电流传感器每个传感器每秒上报一个数值。传统做法是给每个指标设上下限或者用 3-sigma 规则超限就告警。但实际运行中设备启动阶段温度本来就高负载切换时电流本来就会突变这些合法波动很容易触发误报。更麻烦的是多变量之间的耦合。单看电机电流是正常的单看轴承温度也是正常的但如果电流升高而冷却水流量没跟上这就是一个早期故障信号。这种异常不是某个变量越界而是变量之间的相关结构发生了变化。单纯在原始数值上做阈值判断完全发现不了。用深度学习模型直接建模多变量时序也有难点。常见做法是用 LSTM、Transformer 这类序列模型做重构或预测再用重构误差判断异常。这类方法能学到时序依赖但存在两个现实问题一是序列模型训练成本高收敛慢二是长序列场景下梯度传播路径长时序依赖很容易被稀释。对于大多数工业项目来说数据量不够大、标注不够全直接训一个大规模时序模型并不划算。那换个角度想如果能把一段多变量时序数据转换成一张图片是不是就能借用图像领域成熟的预训练模型和分类思路这就是 TS2ITime Series to Image方法的核心逻辑。PRISM 这个研究方向本质上是把异常检测从序列建模问题重新定义成图像识别问题。2. TS2I 的核心思想为什么把时序变成图像TS2I 的全称是 Time Series to Image中文可以叫时序到图像的转换。核心思想并不复杂将一段时间窗口内的多变量时序数据通过某种编码规则映射为一张或多张二维图像然后用图像领域的方法来处理。这里要打消一个常见的误解TS2I 不是把时序数据画成折线图再用 CNN 去看图。折线图是给人看的可视化信息密度低而且像素分布不稳定。真正的 TS2I 有一套严格的数学编码让转换后的图像在结构上保留时序数据的相关性和周期性。TS2I 最大的改变是把异常检测的问题形态变了。原来你面对的是几百步的序列要建模时间依赖和变量相关性转换之后你面对的是几十乘几十或者两百乘两百的图像可以用预训练的视觉模型提取特征。图像的特征提取器已经非常成熟ResNet、EfficientNet 这类网络能捕捉丰富的空间纹理模式而这些空间纹理模式恰好可以编码时序数据中的周期、趋势和突变。用一个类比来理解一句话说机器运行正常人类理解这句话需要语法和上下文但你把它写成工整的汉字一个不认识这句话的人也可以通过笔画的疏密、结构是否匀称来判断它是否怪异。TS2I 相当于把时序数据写成了图像让后续模型不一定要逐点理解时序语义只需要识别图像纹理是否正常。3. 主流 TS2I 转换方法GAF、MTF、RP 与频谱图TS2I 不是单一方法而是一族方法的统称。实际研究和工程中常见的时序到图像的编码方式有以下几种。3.1 GAFGramian Angular Field格拉姆角场GAF 是使用最广泛的 TS2I 方法之一。它将一维时序数据映射到极坐标空间再通过三角函数构造一个二维矩阵。基本步骤将原始序列最小最大归一化到 [-1, 1] 或 [0, 1]。将归一化后的值转换为极坐标角度phi arccos(x)时间轴映射为半径。计算两个时间点之间的角度差或角度和得到 GASF角度和或 GADF角度差矩阵。GAF 图像中的像素点代表两个时间点之间的关系因此能保留时间相关性和局部结构。它适合捕捉周期性异常和趋势突变但缺点是计算量随序列长度呈平方增长实际应用中通常先切滑窗。3.2 MTFMarkov Transition Field马尔可夫转移场MTF 的思路完全不同。它先把时序数据按数值范围划分成 Q 个分位数区间bin然后统计相邻时间点落在不同区间之间的转移概率形成一个 Q×Q 的马尔可夫转移矩阵。再把这个矩阵按时间顺序展开成二维图像。MTF 适合捕捉状态转移异常比如某个过程突然从稳定状态跳变到不稳定状态。它的优点是能体现动态变化的概率结构缺点是需要合理选择分位数数量而且对短序列不太友好。3.3 RPRecurrence Plot递归图RP 是相空间重构的一种可视化方法。它计算时序数据中任意两个时间点之间的距离并将其二值化或灰度化形成递归图。递归图中的纹理模式能反映系统的混沌程度和周期结构。RP 的物理意义直观如果系统行为是周期的递归图会出现明显的对角线纹理如果系统混乱递归图呈现随机散点。对于旋转机械、振动信号这类有周期性特征的场景RP 往往能提供很好的视觉特征。3.4 频谱图时频图频谱图不太一样它是把人眼直观的时频信息直接作为图像。通常用短时傅里叶变换STFT或小波变换将一维信号转换为时间-频率-幅值的三维表示投影到二维平面就是一张时频图。频谱图在语音、声学、振动信号中应用很广但它是单通道的处理多变量时通常要每个通道单独转换然后堆叠成多通道图像。3.5 方法对比方法核心编码逻辑适合的异常类型计算复杂度注意事项GAF极坐标映射 三角变换周期异常、趋势突变O(n²)对归一化敏感MTF分位数状态转移概率状态跳变、动态变化O(Q²n)需要选分位数数量RP时间点距离矩阵混沌程度变化、周期性变化O(n²)阈值选择影响大STFT/小波时频能量分布频段异常、频率漂移O(n log n)窗口大小影响分辨率从 PRISM 这类研究的视角看单独使用其中一种变换往往不够。多变量场景下不同的变量可能表现出不同类型的异常好的 TS2I 表示需要在图像编码层面同时保留变量自身的时序特征和变量之间的一致性特征。4. PRISM 视角下的关键设计多变量通道、归一化与滑窗从论文标题来分析PRISM 的关键词是 Powerful Representations也就是强大的表示。它要解决的不只是一个转换方法而是一套完整的多变量异常检测表示方案。综合这个领域的常见研究方向可以推断这类方案至少需要考虑三个层面的设计。4.1 多变量之间的通道组织方式单变量时序可以转换成一幅图但多变量场景下有几十路信号怎么组织最简单的做法是每个变量单独转换然后像 RGB 三通道一样堆叠起来。但这样做的问题在于如果变量数量少于通道数需要做通道扩充如果变量数量多于通道数需要做通道选择或分组。更复杂的做法是在编码阶段加入变量间的关系信息比如先计算变量间的相关系数矩阵再把相关系数作为某种注意力权重融合到图像中。从工程角度看通道堆叠是最稳妥的起步方式变量间的耦合关系可以交给后续 CNN 层自动学习。4.2 尺度归一化策略多变量数据最大的坑是量纲不一致。电流可能是 0 到 100 安培温度可能是 20 到 80 摄氏度振动加速度可能是 0 到 10 m/s²。如果不做归一化GAF 的极坐标映射会把幅值大的变量完全主导幅值小的变量在图像中的纹理几乎不可见。常见的做法是逐变量做 Min-Max 归一化或 Z-Score 标准化。但要注意工业时序数据经常出现尖峰Min-Max 归一化会被极大值影响导致正常数据在 [0, 1] 区间内被压缩得很扁。更稳妥的做法是先用分位数截断异常尖峰再做归一化。4.3 滑窗长度与重叠策略TS2I 转换将一段连续时序变成一个图像这个一段连续时序就是滑窗。窗口长度直接决定图像尺寸和语义粒度。窗口太短图像信息不足窗口太长异常事件会被正常时段稀释。比较推荐的做法是以目标业务时间尺度为基准。比如振动异常通常持续 5 秒到 10 秒采样频率 100 Hz那么窗口取 512 或 1024 个采样点比较合理。相邻窗口之间按 50% 或 75% 重叠滑动可以保证异常事件不会恰好落在窗口边界被截断。从 PRISM 这类方法的设计逻辑来看一个好的时序表示不是简单地把数据画成图而是要在时间尺度、变量尺度、异常事件尺度之间取得平衡把异常模式在图像中尽可能突出。5. 环境准备与数据集选择实战环节我们来定义一个最小可运行的多变量异常检测项目。这里不绑定特定论文实现而是演示 TS2I 方法的通用工程链路你可以把它替换成 PRISM 论文的具体模型。建议环境如下版本以实际项目为准不用追求最新Python 3.8 或 3.10numpy、pandas 用于数据处理scipy 用于信号处理matplotlib 用于可视化scikit-learn 用于评估指标torch 或 tensorflow 用于 CNN 特征提取本文示例用 torch公开数据集方面多变量时序异常检测领域常用的有 SWaT、WADI、MSL、SMAP、PSM 等。这些数据集包含多种传感器通道和带标注的异常事件。如果你刚接触这个方向建议从小数据集开始跑通流程再迁移到业务数据。注意以下示例代码目的是演示完整的 TS2I 检测链路不依赖任何未公开的论文 API所有库都是常用的开源库可以直接运行。6. 完整示例TS2I 多变量异常检测最小实现整个流程分为四步构造多变量测试数据、GAF 图像转换、CNN 特征提取与异常判别、结果评估。我们用一个合成数据来演示方便你理解每一环节的作用。6.1 构造多变量测试数据先创建一个包含两个变量的模拟时序一个正常正弦信号一个在中间时段发生幅值突变的异常信号。# 文件路径ts2i_demo/data_generator.py import numpy as np def generate_synthetic_data(length2048, anomaly_start1200, anomaly_end1400): t np.arange(length) # 变量1正常正弦波 var1 np.sin(2 * np.pi * 2 * t / length) 0.1 * np.random.randn(length) # 变量2正弦波 线性趋势 var2 np.sin(2 * np.pi * 5 * t / length) 0.01 * t 0.1 * np.random.randn(length) # 在异常区间给变量1加上幅度突变 var1[anomaly_start:anomaly_end] 3.0 data np.vstack([var1, var2]).T labels np.zeros(length) labels[anomaly_start:anomaly_end] 1 return data, labels这段代码生成了两个变量正常状态下是正弦信号叠加轻微噪声异常区间是变量1幅值突变。labels是逐点的异常标注用来评估检测效果。6.2 GAF 图像转换模块接下来实现 Gramian Angular Field 转换。这是 TS2I 链路最核心的部分输入是一段多变量滑窗数据输出是多通道图像。# 文件路径ts2i_demo/gaf.py import numpy as np def min_max_scale(series, lower-1.0, upper1.0): 将序列缩放到 [lower, upper] 区间并处理常数序列 s_min series.min() s_max series.max() if s_max - s_min 1e-9: return np.full_like(series, 0.0) scaled (series - s_min) / (s_max - s_min) return scaled * (upper - lower) lower def gramian_angular_field(series): 计算 GADFGramian Angular Difference Field 输入一维序列 输出n x n 的图像矩阵 # 1. 缩放到 [-1, 1] scaled min_max_scale(series) # 2. 极坐标转换 phi np.arccos(scaled) # 3. GADF 矩阵 sin_phi np.sin(phi) cos_phi np.cos(phi) gadf sin_phi[:, None] * cos_phi[None, :] - cos_phi[:, None] * sin_phi[None, :] return gadf def sliding_window_gaf(data, window_size128, stride64): 将多变量时序数据转换为多通道 GAF 图像 输入data shape [n_timesteps, n_channels] 输出images shape [n_windows, n_channels, window_size, window_size] n_timesteps, n_channels data.shape images [] for start in range(0, n_timesteps - window_size 1, stride): window data[start:start window_size, :] channel_images [] for ch in range(n_channels): gaf_img gramian_angular_field(window[:, ch]) channel_images.append(gaf_img) # 通道维度放在第 1 维符合 PyTorch 的 [N, C, H, W] 布局 images.append(np.stack(channel_images, axis0)) return np.array(images)这里要解释几个设计选择。GADF使用的是角度差比GASF能更好地区分小幅变化在异常检测中通常表现更好。sliding_window_gaf返回的数组形状是[窗口数, 变量数, 窗口长度, 窗口长度]对应 PyTorch 的[N, C, H, W]可以直接输入 CNN。6.3 CNN 特征提取与异常打分GAF 图像生成后用一个轻量 CNN 提取特征再用一类分类的思路做异常检测。这里的关键是我们只用正常数据训练 CNN 的分类头或特征提取器异常数据在特征空间中会远离正常类中心。# 文件路径ts2i_demo/ts2i_detector.py import torch import torch.nn as nn import torch.optim as optim from sklearn.covariance import EmpiricalCovariance class EncoderCNN(nn.Module): def __init__(self, in_channels): super().__init__() self.features nn.Sequential( nn.Conv2d(in_channels, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.BatchNorm2d(128), nn.ReLU(), nn.AdaptiveAvgPool2d(1), ) self.projection nn.Sequential( nn.Flatten(), nn.Linear(128, 64) ) def forward(self, x): feat self.features(x) return self.projection(feat) class TS2IAnomalyDetector: def __init__(self, in_channels, devicecpu): self.device torch.device(device) self.model EncoderCNN(in_channels).to(self.device) self.cov_estimator None def fit(self, normal_images, epochs30, lr1e-3): 只用正常样本训练目标是最小化特征向量的分布半径 dataset torch.tensor(normal_images, dtypetorch.float32).to(self.device) loader torch.utils.data.DataLoader(dataset, batch_size32, shuffleTrue) optimizer optim.Adam(self.model.parameters(), lrlr) criterion nn.MSELoss() self.model.train() for epoch in range(epochs): total_loss 0.0 for batch in loader: optimizer.zero_grad() features self.model(batch) # 拉向中心点的对比式损失自监督逼近零向量 loss criterion(features, torch.zeros_like(features)) loss.backward() optimizer.step() total_loss loss.item() if (epoch 1) % 10 0: print(fEpoch {epoch1}/{epochs}, Loss: {total_loss/len(loader):.6f}) # 使用马氏距离计算异常分数 with torch.no_grad(): train_features self.model(dataset).cpu().numpy() self.cov_estimator EmpiricalCovariance().fit(train_features) def predict_score(self, images): self.model.eval() with torch.no_grad(): features self.model(torch.tensor(images, dtypetorch.float32).to(self.device)).cpu().numpy() mahalanobis self.cov_estimator.mahalanobis(features) return mahalanobis这个实现使用了一种常见的无监督异常检测思路训练 CNN 将正常窗口的 GAF 图像映射到特征空间然后用马氏距离Mahalanobis Distance度量新样本偏离正常分布的程度。EmpiricalCovariance来自 scikit-learn直接计算均值和协方差不需要额外安装。要说明的是这里用压向零向量的损失函数只是为了让特征空间在训练时有一个稳定的收敛目标更完整的 PRISM 方案可能会使用对比学习、自编码器或分类损失你可以根据自己的数据尝试不同策略。6.4 主流程运行脚本# 文件路径ts2i_demo/main.py import numpy as np from data_generator import generate_synthetic_data from gaf import sliding_window_gaf from ts2i_detector import TS2IAnomalyDetector # 1. 生成数据 data, labels generate_synthetic_data() # 2. 只在正常区间上切窗用于训练 train_data data[:1000] train_images sliding_window_gaf(train_data, window_size128, stride32) # 3. 全量数据切窗用于测试 test_images sliding_window_gaf(data, window_size128, stride32) # 4. 训练异常检测器 detector TS2IAnomalyDetector(in_channelstrain_images.shape[1]) detector.fit(train_images, epochs20) # 5. 对测试窗口打分 scores detector.predict_score(test_images) # 6. 将窗口级分数映射回点级分数 point_scores np.zeros(len(data)) window_count 0 for start in range(0, len(data) - 128 1, 32): point_scores[start:start 128] np.maximum( point_scores[start:start 128], scores[window_count] ) window_count 1 # 7. 使用阈值判定异常 threshold np.percentile(point_scores[:1000], 95) pred_labels (point_scores threshold).astype(int) # 8. 输出评估指标 from sklearn.metrics import f1_score, precision_score, recall_score print(fPrecision: {precision_score(labels, pred_labels):.4f}) print(fRecall: {recall_score(labels, pred_labels):.4f}) print(fF1: {f1_score(labels, pred_labels):.4f})主流程的逻辑很清晰先用前 1000 个正常点训练然后对整个序列生成窗口分数再通过取最大值的方式把窗口分数展开到点级。这里要注意窗口分数的展开策略是系统实现的关键点不同策略对异常事件边界检测的敏感性不同。7. 运行结果与验证方式如果你在合成数据上运行上面的代码预期会看到类似这样的输出实际数值因环境不同会略有差异Epoch 10/20, Loss: 0.002734 Epoch 20/20, Loss: 0.000812 Precision: 0.8523 Recall: 0.9134 F1: 0.8819注意这个效果是基于合成数据实际业务数据上的效果取决于你的窗口长度、归一化策略和模型结构。运行后需要检查三个关键指标训练 loss 是否持续下降。如果 loss 波动很大或直接发散优先检查学习率是否过大或者数据是否包含 NaN。点级分数在异常区间是否有明显峰值。如果分数没有在异常区间升高说明窗口长度可能太长异常事件被正常时段稀释了或者 GAF 归一化被异常的极大值污染。Precision 和 Recall 是否平衡。工业场景通常更关注 Recall宁可多报几次误报也不希望漏报因为漏报意味着实际的设备故障没有被发现。判断运行成功最直接的方式是绘制原始信号 异常分数的对照图观察异常分数是否在真值标注的区间附近出现清晰的峰值。# 文件路径ts2i_demo/visualize.py import matplotlib.pyplot as plt plt.figure(figsize(14, 8)) plt.subplot(3, 1, 1) plt.plot(data[:, 0], labelvar1) plt.plot(data[:, 1], labelvar2) plt.legend() plt.title(Multivariate Time Series) plt.subplot(3, 1, 2) plt.plot(point_scores, labelanomaly score, colorred) plt.axhline(threshold, colorblack, linestyle--, labelthreshold) plt.legend() plt.title(Anomaly Score) plt.subplot(3, 1, 3) plt.plot(labels, labelground truth, colorgreen) plt.plot(pred_labels, labelprediction, colororange, alpha0.7) plt.legend() plt.title(Label vs Prediction) plt.tight_layout() plt.savefig(result.png)如果第二张子图中异常分数在中间异常区间出现明显凸起说明整个 TS2I 链路是通的。8. 常见问题与排查思路TS2I 方法在实际运行中会碰到一些典型问题这里给出经过整理的排查清单。问题现象可能原因排查方式解决方案GAF 图像几乎全黑或全白归一化被极大/极小值污染打印归一化后序列的 min/max使用分位数截断或 RobustScaler模型训练 loss 不下降学习率过大或特征维度太小检查 loss 曲线和梯度范数降低学习率到 1e-4或增大投影维度异常分数在所有窗口都差不多滑窗太长异常被稀释缩短窗口并提高重叠率用业务异常持续时间估算窗口长度检测出的异常位置有偏移窗口展开策略导致边界模糊对比窗口级分数和点级标注使用取最大值扩展而不是平均值多变量场景下某些通道不敏感不同变量量纲差异大检查各通道图像灰度分布逐通道独立归一化工业指标波动剧烈误报多正常模式本身就多变检查训练数据是否覆盖全部工况增加工况标签或使用条件归一化一个特别容易踩的坑是训练数据里混入了少量异常数据。TS2I 的单类训练方法假设训练集全是正常样本如果里面混有异常窗口模型会把异常模式也学成正常导致实际检测时漏报。解决方法是做一轮简单的预筛选先用规则或 3-sigma 粗略剔除明显异常区间再训练正式模型。9. 最佳实践与工程落地建议TS2I 方法从论文到工业落地中间还有一段路要走。结合工程部署的常见问题整理几个关键建议。9.1 窗口参数设计要回到业务场景不要盲目套默认参数。窗口长度应该由异常事件的最小持续时间决定。比如一个故障在振动信号中至少持续 0.5 秒采样频率 100 Hz那么窗口至少要有 50 个点。一般取异常持续时间的 2 到 4 倍作为窗口长度能让异常事件在窗口内占比合理既不会被稀释也不会被截断。9.2 用 KPI 对齐评估指标异常检测的评估不能只看准确率。在多变量时序场景里正常样本占比可能超过 99%一个永远预测正常的模型准确率也会很高但毫无意义。工业项目里更推荐关注 Recall 和 F1并且要按事件级别统计。点级别的 Precision/Recall 会被连续异常区间放大事件级别的统计方式更能反映实际告警质量。9.3 训练数据要覆盖所有正常工况工业设备有不同的运行工况启机、稳态运行、负载切换、停机。每个工况下的数据分布差异很大。如果训练数据只覆盖了稳态工况那么负载切换产生的数据会被误判为异常。解决办法有两个一是按工况分组训练多个模型二是在特征向量中显式加入工况编码让模型学到工况条件化的正常模式。9.4 从图像分数到告警策略的映射TS2I 模型输出的是连续分数不是直接告警。合理的设计是分数超过高阈值时立即告警超过低阈值时进入观察状态在连续多个窗口都超过低阈值时才升级告警。这种两级阈值 延时确认的策略能显著降低误报率。9.5 版本管理与回滚机制如果模型在线上运行每次更新都会有风险。建议将模型推理和告警决策解耦模型 A 负责输出异常分数规则引擎负责消费分数并做最终告警判断。这样模型升级时只需要灰度切换打分服务告警策略保持不变如果新模型指标变差可以快速回滚到旧模型。相关服务模块要保留上一版本的模型文件和配置文件确认新模型稳定后再清理。9.6 日志与可解释性工业场景中算法判断异常后运维人员需要知道为什么异常。建议在打分服务中记录每个异常窗口对应的通道、GAF 图像、Top 贡献特征维度和对应的时间区间。这些信息既方便事后复盘也能帮助后续优化模型。10. 总结与后续学习方向这篇文章从多变量时序异常检测的痛点出发分析了 PRISM 这类 TS2I 方法的核心思想通过 GAF、MTF、RP 等方法把时序数据编码为图像把异常检测从序列建模问题转化为图像识别问题从而复用视觉领域的成熟模型。文章中给出的最小工程链路覆盖了数据构造、GAF 转换、CNN 特征提取、异常打分和评估验证可以用作你接入 PRISM 论文方案之前的基线参考。如果你准备深入这个方向下一步可以优先做三件事一是用公开数据集SWaT、MSL、SMAP跑通 GAF 和 MTF 的对比实验理解不同编码方式对异常类型的敏感性二是研究对比学习在时序表示中的应用PRISM 这类强大表示通常依赖更好的特征学习方法而不仅仅是转换方式本身三是把模型部署为独立的打分服务接入实际指标流用真实数据验证误报率和延迟。异常检测项目最核心的工程能力是在漏报和误报之间找到平衡这种平衡只有通过业务数据反复迭代才能建立。
返回列表