1. 项目概述与核心价值在工业物联网、金融风控和智能运维这些领域我们每天都在跟海量的传感器数据、交易流水或系统指标打交道。这些数据天然就是多变量时间序列几十甚至上百个指标每个指标都随着时间不断变化并且彼此之间还存在着千丝万缕的联系。比如一个机房的CPU利用率飙升往往伴随着内存使用量的增长和网络流量的异常金融市场上一只股票的异动也可能引发相关板块的连锁反应。从这些复杂、高维且相互关联的数据流中精准地揪出那个“不对劲”的点就是多变量时间序列异常检测的核心任务。然而干了这么多年数据分析和算法开发我深感一个痛点“巧妇难为无米之炊”更难受的是“米”的质量还参差不齐。我们评估一个异常检测模型好不好严重依赖公开或私有的数据集。但现实是很多数据集要么异常标签是粗粒度的只告诉你某段时间“有问题”但不说是哪个变量引起的要么变量间的因果关系是模糊的、甚至是被忽略的。这就好比考驾照如果路考路线不明确、评分标准模糊你怎么能公正地比较两个学员的驾驶水平同样在这种“黑盒”数据集上评估模型结果的可信度和可解释性都会大打折扣。模型可能整体检测准确率很高但你完全不知道它是不是蒙对的或者它是否真的理解了变量间的动力学关系。Fun-TSG就是为了解决这个根本性难题而生的工具。它不是一个简单的随机数生成器而是一个基于函数驱动建模的、高度可控的多变量时间序列合成引擎。它的核心思想非常直观且强大既然真实世界的时序数据是由某些潜在的物理规律或业务逻辑可以抽象为数学函数生成的那么我们就用一套清晰的、可解释的函数方程组来模拟这个过程。每个变量比如温度、压力、转速的值都由一个数学函数决定这个函数的输入可以是它自身的历史值也可以是其他变量的当前或历史值。这些依赖关系被明确地定义在一个有向图中。在这个清晰的基础上我们再通过“篡改”这些生成函数来制造“异常”并且能精确地记录下异常发生在哪个时间点、影响了哪个变量、以及这个异常是如何通过依赖关系波及其他变量的。这样一来Fun-TSG 生成的数据集就拥有了“上帝视角”的完整真相每一个数据点为什么是那个值每一个异常是如何被“植入”的都一清二楚。这为异常检测算法的评估带来了革命性的透明度和可复现性。你可以用它来公平地Benchmark基准测试为不同的检测算法无论是传统的统计方法还是最新的深度学习模型提供一个统一的、标准化的“考场”。进行归因分析专门测试那些号称能“定位异常根源变量”的模型如GNN、注意力机制模型看看它们是不是真的找到了正确的“病灶”。可控的消融实验你可以调整数据生成的“旋钮”比如增加变量间依赖的复杂度、改变异常持续的长度和类型、调整噪声水平然后观察你的模型性能如何变化从而深入理解模型的优势和短板。简单说Fun-TSG 让你从“在迷雾中评价模型”变成了“在显微镜下剖析模型”。2. 核心设计思路与原理拆解Fun-TSG 的设计哲学可以概括为“白盒生成精准控制”。它摒弃了像GAN、扩散模型那样通过复杂神经网络隐式学习数据分布的黑盒生成方式而是回归到基于显式数学方程和依赖图的可解释建模。这套方法论背后是对多变量时序数据本质的深刻理解。2.1 核心建模框架有向依赖图与生成函数想象一下一个简单的工业系统一个水箱有进水阀变量A、出水阀变量B和水位变量C。水位C显然依赖于进水A和出水B的历史情况。Fun-TSG 将这种关系形式化为一个有向图 G(V, E)。节点 V就是所有的时序变量比如 A(t), B(t), C(t)。边 E一条从 A 指向 C 的边表示 C 的值部分地由 A可能是当前值也可能是过去某个时刻的值决定。A 就是 C 的一个“父节点”。在这个图中有些变量是“外生变量”它们没有父节点其值由独立的函数如正弦波、随机游走生成模拟外部输入或噪声源。更多的变量是“内生变量”它们的值由其父节点们通过一个生成函数 f计算得出。例如水位 C 的生成函数可能是C(t) 0.8*C(t-1) 0.5*A(t-2) - 0.3*B(t-1) noise。这个函数明确表达了时序依赖C(t-1)和变量间依赖A, B。为什么选择函数建模而不是深度学习生成深度生成模型如GAN虽然能产生看起来很真实的数据但其内部机制不透明你无法精确控制“在时间步t100时对变量C注入一个由函数突变引起的、持续10个时间步的异常”。而函数建模提供了原子级的控制力。你可以像写数学公式一样定义正常行为然后通过修改公式的某一部分来定义异常行为。这种透明性和可控性正是构建可信评估基准所必需的。2.2 两种生成模式自动化与手动配置Fun-TSG 提供了两种互补的生成路径以适应不同的研究需求。2.2.1 自动化随机生成模式这是为了快速、大规模地生成多样化测试场景而设计的。你只需要提供一些高级参数num_variables时间序列的维度变量个数。num_communities将变量分成的“社区”数量。同一社区内的变量依赖更紧密不同社区间依赖稀疏或没有这模拟了现实系统中模块化的特性例如工厂的A生产线和B生产线数据关联较弱。max_indegree单个变量最多可以依赖多少个其他变量即父节点的最大数量控制依赖复杂度。contamination_ratio异常点占总数据点的比例。length生成时间序列的总长度。给定这些参数后Fun-TSG 会执行一个精心设计的自动化流程生成依赖图算法会先根据社区数划分变量然后在每个社区内部随机生成连接边确保图是连通的信息可以传递。最后可以选择性地在社区间添加少量“跨社区链接”模拟罕见的远程耦合。为每个变量生成生成函数这是核心步骤。算法从一个函数库F包含, -, *, /, sin, cos, exp, log等基本运算和函数中随机选择操作符以变量的父节点集合和随机常数为“叶子”自底向上地构建一棵语法树。为了防止生成爆炸增长或无意义的函数如exp(exp(x))算法引入了“增长分数”机制来平衡函数的选择概率。注入异常算法根据污染率随机选择异常发生的时间段和受影响的变量。然后通过三种策略之一修改该变量正常的生成函数语法树插入一个新子树、删除一个子树并用常数替代、替换一个操作符节点。同时算法会为依赖图中的每条边随机分配“传播行为”决定父变量的异常值是否会影响子变量的计算从而模拟异常在系统中的传播或隔离。2.2.2 手动方程配置模式当你需要针对一个非常具体的、假设性的场景进行测试时自动模式可能不够精确。手动模式允许你完全“编剧”。你可以为每一个变量x_j直接写出其生成方程例如x2(t) sin(0.1*t) 0.7*x1(t-1) - 0.2*x3(t-2)。你可以精确地定义依赖图谁依赖谁时滞是多少。你可以指定在t150到t160期间变量x2的生成函数临时变为x2(t) 2.0 * sin(0.1*t)一个幅度突增的异常。这种模式赋予了研究者终极的控制权非常适合用于教学、算法原理验证或对特定类型异常如缓变、阶跃、周期性破坏的针对性研究。2.3 精细化的异常标签体系Fun-TSG 的标签系统是其一大亮点远超简单的0/1二分类。y0正常点。y1异常点。该点的值是由“异常函数”生成的。y2传播隔离点。这个点本身的值是按“正常函数”计算的但其某个父节点的输入值是异常的。然而由于依赖边的“传播行为”被设置为不传播这个异常输入并未导致该点输出异常。这标记了异常影响的边界。y3传播影响点。这个点本身的值是按“正常函数”计算的但其某个父节点的输入值是异常的并且该异常被传播了过来导致该点输出异常。这标记了异常传播的路径。这个四分类标签体系为评估模型的根因定位和影响范围分析能力提供了黄金标准。一个好的模型应该不仅能检测出y1的点还能识别出y3的点是受牵连的而y2的点是安全的。3. 实操指南从安装到生成你的第一个数据集理论讲得再多不如亲手跑一遍。下面我将带你一步步使用 Fun-TSG生成你的第一份合成数据。3.1 环境准备与工具获取Fun-TSG 是一个 Python 项目。建议使用 Python 3.8 及以上版本。第一步获取代码工具是开源的你可以直接从其 GitLab 仓库克隆git clone https://gitlab.irit.fr/sig/theses/pierre-lotte/Fun-TSG.git cd Fun-TSG如果网络访问仓库有困难论文中也提供了一个打包好的压缩文件备用链接里面包含了源代码、文档和一些示例数据集。第二步安装依赖项目根目录下通常会有一个requirements.txt或pyproject.toml文件。使用 pip 安装即可pip install -r requirements.txt核心依赖通常包括numpy,sympy用于符号计算和函数处理,networkx用于图操作,matplotlib用于绘图等。如果安装sympy遇到问题可以尝试先升级 pip。第三步验证安装尝试运行工具自带的一个示例脚本或查看帮助信息确保环境正确。python -c import fun_tsg; print(fun_tsg.__version__) # 如果提供了版本号 # 或者 python main.py --help3.2 使用自动模式快速生成基准数据假设我们想生成一个包含5个变量、2个社区、中等依赖复杂度、包含5%异常点的数据集用于初步测试一个算法。我们可以编写一个简单的 Python 脚本generate_auto.pyimport fun_tsg import numpy as np import matplotlib.pyplot as plt # 1. 设置生成参数 config { num_variables: 5, num_communities: 2, max_indegree: 3, contamination_ratio: 0.05, time_series_length: 1000, link_communities: True, # 允许社区间有少量连接 noise_level: 0.02, # 添加少量高斯噪声增加真实性 seed: 42, # 固定随机种子确保结果可复现 } # 2. 实例化生成器并生成数据 generator fun_tsg.AutoGenerator(**config) data, labels, graph_info, equations generator.generate() # data 是一个形状为 (1000, 5) 的 numpy 数组 # labels 是一个形状为 (1000, 5) 的数组每个元素是 0,1,2,3 中的一个 # graph_info 包含了依赖图的结构信息 # equations 是一个列表包含了每个变量的生成函数字符串形式 print(f数据形状: {data.shape}) print(f标签形状: {labels.shape}) print(f变量0的生成方程示例: {equations[0]}) # 3. 可视化前两个变量 fig, axes plt.subplots(2, 1, figsize(12, 6)) time np.arange(config[time_series_length]) for i in range(2): axes[i].plot(time, data[:, i], labelfVar {i}, linewidth0.8) # 高亮显示异常点 (y1) anomaly_idx np.where(labels[:, i] 1)[0] if len(anomaly_idx) 0: axes[i].scatter(anomaly_idx, data[anomaly_idx, i], colorred, s10, labelAnomaly (y1), zorder5) axes[i].set_ylabel(fValue Var{i}) axes[i].legend() axes[i].grid(True, linestyle--, alpha0.6) axes[-1].set_xlabel(Time Step) plt.suptitle(Automatically Generated Multivariate Time Series (First 2 Variables)) plt.tight_layout() plt.savefig(auto_generated_series.png, dpi150) plt.show() # 4. 保存生成的数据和元数据 np.save(auto_generated_data.npy, data) np.save(auto_generated_labels.npy, labels) # 可以保存图信息和方程供后续分析 import json with open(graph_info.json, w) as f: # 注意graph_info可能需要转换为可序列化格式 json.dump(str(graph_info), f) with open(equations.txt, w) as f: for i, eq in enumerate(equations): f.write(fVar{i}: {eq}\n)关键操作解析与注意事项随机种子在科研中可复现性至关重要。务必设置seed参数这样每次运行都能得到完全相同的数据集便于算法间的公平比较和调试。参数理解max_indegree不宜设置过大如超过5否则生成的函数会过于复杂可能导致数值计算不稳定如溢出。num_communities应小于num_variables合理的设置能生成更符合现实模块化系统的数据。数据审视生成后一定要像上面代码那样先可视化查看一下。检查数据尺度是否合理有无NaN或无穷大异常点是否肉眼可见但又不至于太明显。这是确保生成过程健康的第一步。3.3 使用手动模式进行精细控制现在假设我们想研究一个特定的异常传播场景一个中心变量异常如何影响其下游变量。我们可以手动定义这个系统。创建一个脚本generate_manual.pyimport fun_tsg import numpy as np # 1. 手动定义系统 # 假设我们有3个变量x0, x1, x2 # x0 是外生变量遵循正弦波 # x1 依赖于 x0 的滞后值 # x2 依赖于 x1 的当前值和 x0 的滞后值 manual_config { variables: { x0: { equation: sin(0.05 * t) 0.1 * normal_noise(t), # t是时间索引normal_noise是工具内置的噪声函数 is_exogenous: True }, x1: { equation: 0.8 * x1(t-1) 0.5 * x0(t-2) - 0.1 * x0(t-1), parents: [x0], # 声明依赖关系时滞在方程中体现 is_exogenous: False }, x2: { equation: 0.6 * x2(t-1) 0.3 * x1(t) - 0.2 * x0(t-3), parents: [x1, x0], is_exogenous: False } }, time_series_length: 500, default_noise: 0.01, } # 2. 定义异常 # 我们想在 t200 到 t220 期间让 x1 的生成函数发生突变系数改变 anomalies [ { variable: x1, start: 200, end: 220, # 包含200不包含220需根据工具API确定通常是[start, end)区间。 anomalous_equation: 0.2 * x1(t-1) 1.5 * x0(t-2) - 0.1 * x0(t-1), # 改变了动态特性 propagation_rules: {(x0, x1): propagate, (x1, x2): propagate} # 明确指定传播行为 } ] # 3. 生成数据 generator fun_tsg.ManualGenerator(variables_configmanual_config[variables], lengthmanual_config[time_series_length], noise_levelmanual_config[default_noise]) data, labels, meta generator.generate(anomaliesanomalies) print(手动生成完成。) print(fx1在时间步205的标签是: {labels[205, 1]}) # 应该是1 print(fx2在时间步205的标签是: {labels[205, 2]}) # 可能是3如果传播了 # 4. 分析标签分布 unique, counts np.unique(labels, return_countsTrue) label_map {0: 正常, 1: 异常源, 2: 传播隔离, 3: 传播影响} for val, count in zip(unique, counts): print(f标签 {val} ({label_map.get(val, 未知)}): {count} 个点)手动模式的核心要点方程书写方程字符串的解析是核心。需要严格按照工具支持的语法来写。通常支持常见的数学函数、运算符并使用x_i(t-k)表示变量x_i在t-k时刻的值。t代表当前时间索引。务必仔细阅读工具的文档了解其支持的函数库和语法规则。依赖声明在parents列表中声明依赖的变量名即使时滞在方程中体现这里也需要声明以便工具内部构建正确的计算图和执行顺序。异常定义anomalous_equation需要与原始方程在“语法树形状”上兼容吗在Fun-TSG的设计中通常不需要因为它允许通过子树插入、删除、替换来生成异常函数。但在手动模式下你直接提供新方程需要确保其自变量父变量是有效的否则计算时会出错。传播规则这是手动模式的强大之处。你可以精确控制异常是否沿着某条边传播。这对于研究系统的鲁棒性或脆弱性非常有用。4. 高级应用与评估框架搭建生成了数据只是第一步如何用它来有效地评估你的异常检测模型才是关键。下面分享一套基于 Fun-TSG 的评估流程和心得。4.1 设计全面的评估实验不要只用一个数据集、一种参数配置来评价模型。利用 Fun-TSG 的可控性设计一个多维度的评估矩阵复杂度扫描变量数生成变量数从5、10、20、50不等的序列测试模型对高维数据的处理能力。依赖强度通过调整max_indegree和社区内连接概率生成从稀疏连接到稠密连接的不同图结构检验模型捕捉变量关系的能力。时序动态在手动模式中使用更复杂的函数如积分、非线性组合来生成数据测试模型对复杂动态的建模能力。异常类型挑战点异常 vs 集体异常通过控制anomaly_length的分布生成既有瞬时尖峰又有持续一段时间的异常。幅值异常 vs 模式异常在手动模式中可以定义不同类型的异常方程。例如幅值异常*2.0均值漂移5.0动态改变系数突变甚至频率变化。用这些数据测试模型对不同异常形态的敏感性。噪声鲁棒性测试在相同基础生成过程下生成不同noise_level如0, 0.01, 0.05, 0.1的数据集。观察模型的 F1-Score 或 AUC 如何随噪声增加而下降这能反映模型的稳定性。4.2 超越点级评估利用精细标签传统的评估通常只关注“是否检测到了异常时间点”点级评估。Fun-TSG 的精细标签允许我们进行更深入的评估变量级定位评估对于每个被模型标记为异常的时间段检查模型是否能够输出“最可能出错的变量”。然后与真实标签y1的变量进行对比。可以计算变量级的精确率、召回率。评估指标建议对于每个异常事件计算模型预测的根因变量集合与真实根因变量集合的Jaccard 相似系数或F1分数。这比简单的“对/错”更能反映定位的准确性。传播路径分析对于模型检测到的异常点如果模型能提供异常分数或注意力权重可以分析这些分数/权重是否在真实的传播路径y3的变量上也较高。这可以评估模型对异常传播的感知能力。可以绘制一个“异常热力图”横轴是时间纵轴是变量用颜色深浅表示模型的异常分数与真实的y1,2,3标签叠加对比一目了然。设计专门的“归因诊断数据集”使用手动模式构建一个中心变量异常并通过明确的、分叉的依赖路径向下游传播的简单系统。测试那些声称具有可解释性的图神经网络GNN或注意力机制模型看它们的注意力权重是否真的集中在了正确的传播路径上。这是对模型可解释性“真实性”的绝佳检验。4.3 实操心得与避坑指南在长期使用和测试类似工具的过程中我积累了一些经验教训数据标准化是关键前置步骤Fun-TSG 生成的各变量数据可能尺度差异很大比如有的变量是正弦波在[-1,1]有的可能是指数增长。在将数据输入你的模型之前一定要进行按变量的标准化Z-score或归一化。否则尺度大的变量会主导模型的损失函数严重影响性能。记得将标准化参数均值和标准差保存下来用于对预测结果进行反标准化。警惕“过拟合生成过程”如果你的模型特别是深度模型在某个特定参数配置生成的训练集上表现极好但在其他配置或真实数据上表现骤降它可能只是记住了这个简单生成过程的一些表面特征而非学会了通用的异常检测模式。务必使用多种不同参数配置生成的数据来训练和验证模型以增强其泛化能力。理解“传播行为”的设定在评估归因能力时y2传播隔离和y3传播影响的设定至关重要。如果你的模型将所有y2和y3的点都预测为异常说明它可能过度依赖变量间的相关性而没有理解真正的因果或影响机制。一个健壮的模型应该能区分“因”和“果”。可视化可视化再可视化在生成数据后、训练模型前、评估结果后都要进行大量的可视化。画出时间序列图、依赖图、异常标签热力图、模型的异常分数图。视觉检查能帮你快速发现数据生成的问题如函数爆炸、模型行为的诡异之处如对所有变量都给出相似的异常分数。将合成数据与真实数据结合使用Fun-TSG 生成的数据是“纯净”的有明确的机制。但它无法完全模拟真实世界的所有复杂性和未知因素。最佳实践是用 Fun-TSG 数据进行算法原理验证、消融实验和可控性能测试用真实的、标注良好的数据集如TSB-AD、SWaT、SMAP进行最终的性能验证和比较。合成数据帮你“理解”算法真实数据帮你“证明”算法。5. 常见问题与排查技巧实录即使有了强大的工具在实际操作中还是会遇到各种问题。下面是我遇到的一些典型问题及解决方法。问题1生成的序列出现 NaN非数字或 Inf无穷大值。原因这通常是由于生成函数中包含了一些非法运算例如对负数取对数log(x)当x0。除以零或非常接近零的数。指数函数导致数值溢出exp(1000)。在自动生成模式下随机组合的函数树可能产生这种病态表达式。排查与解决检查生成方程在手动模式下仔细检查你写的方程。在自动模式下打印出equations看看是哪个变量的方程出了问题。启用值域检查查看 Fun-TSG 是否有参数可以在函数求值时进行值域检查或夹紧clamp。有些实现会内置保护机制。调整函数库在自动生成时可以尝试限制函数库F暂时移除log,div等高风险操作符或者为除法加上一个小的保护常数(x/(y1e-8))。添加后处理生成数据后写一个简单的检查脚本if np.any(np.isnan(data)) or np.any(np.isinf(data)): print(“数据包含无效值正在定位...”) nan_rows, nan_cols np.where(np.isnan(data)) for r, c in zip(nan_rows[:5], nan_cols[:5]): # 打印前几个 print(f”在时间步{r}, 变量{c}发现NaN其方程为: {equations[c]}”) # 可以选择用前后值插值填充或者重新生成 data np.nan_to_num(data, nan0.0, posinf1e6, neginf-1e6) # 应急处理问题2自动生成的依赖图过于稠密或稀疏不符合预期。原因max_indegree参数理解有误或者社区划分与变量数不匹配。解决max_indegree是每个节点最大入度不是平均入度。设成3意味着一个变量最多可以有3个父变量。在变量数少如5时图很容易就变得比较连通。如果希望图更稀疏可以降低max_indegree比如设为1或2或者增加num_communities并设置link_communitiesFalse。可视化依赖图使用networkx和matplotlib将graph_info画出来直观感受结构。import networkx as nx G nx.DiGraph() G.add_nodes_from(range(config[num_variables])) # 根据 graph_info 添加边graph_info 可能包含边列表 # 假设 edges 是一个列表元素是 (parent_idx, child_idx) for edge in graph_info[edges]: G.add_edge(edge[0], edge[1]) nx.draw(G, with_labelsTrue, node_colorlightblue, arrowsTrue) plt.title(Dependency Graph) plt.show()问题3模型在合成数据上表现很好但在真实数据上表现很差。原因这是最常见的“分布外”问题。合成数据的分布和真实数据的分布差异太大。解决策略增加真实感在 Fun-TSG 生成数据后可以添加更复杂的噪声模型如自相关噪声、模拟缺失值、或者对数据进行非线性变换使其统计特性如自相关系数、分布偏度峰度更接近你的目标真实数据。迁移学习/微调先在大量、多样的 Fun-TSG 数据上预训练模型学习捕捉时序依赖和异常模式的基本能力然后在少量真实数据上进行微调。领域适配分析你的真实数据特点如周期性、趋势、突变模式然后使用 Fun-TSG 的手动模式尽可能逼近地模拟这些特点。例如如果你的真实数据有强烈的日周期就在生成函数中加入sin(2*pi*t/24)这样的项。问题4评估指标选择困难特别是对于集体异常和精细标签。建议对于点级检测推荐使用PaRC (Point-wise Adjusted Range-based F1)或Affiliation-based F1。这些指标比传统的点对点F1对集体异常更友好它们允许预测异常段和真实异常段在边界上有一定的错位而不严重惩罚。对于变量级归因如前所述使用基于集合的指标Jaccard, F1。也可以计算Mean Average Precision (MAP)如果你模型的输出是每个变量的异常概率排序的话。不要只看一个指标同时汇报精确率、召回率、F1、以及 AUC如果模型输出连续异常分数。绘制精确率-召回率曲线也能提供更多信息。创建综合评分对于多目标评估既要检测时间又要定位变量可以设计一个加权分数例如综合得分 0.7 * 点级F1 0.3 * 变量级Jaccard。权重的设置取决于你的应用场景更侧重哪方面。Fun-TSG 作为一个强大的数据生成工具其价值不仅在于“造数据”更在于它为我们提供了一个可解释、可操控的实验平台。通过它我们可以像做物理实验一样控制变量观察结果从而更深刻地理解不同异常检测算法的工作原理、优势和局限。将它与真实数据集结合使用取长补短是推动多变量时序异常检测领域向更严谨、更可解释方向发展的有力途径。