文章目录
看到 文章WSDM 2021 | 时间序列转化为动态图进行表示
旨在将时间序列转化为“演化状态图”(Evolutionary State Graph),核心步骤包括:
- 数据离散化与状态定义: 将连续的时间序列数据转化为离散的状态表示。
- 状态图构建: 基于离散状态,构建图的节点和边。
- 图的动态演化: 随着时间的推移,图的结构(节点和边)会动态更新。
论文标题:Time-Series Event Prediction with Evolutionary State Graph
原文地址:https://arxiv.org/abs/1905.05006
模型源码:https://github.com/VachelHU/EvoNet
本篇仅仅只是简单探索一下,该文章如何将时间序列如何转化为动态图这一过程。
1 时间序列的状态的定义
可解释的时序建模多着眼于离散时序,在时间轴上将时序分段,然后从分段中挖掘出有用的特征表示,常见的方法有:
- 字典方法,找时序分段的特征值
- 形状方法,找时序分段的特殊波形
- 聚类方法,找时序分段的分类特征
这些方法可以有效的捕捉时序中的显著特征,即有表征意义的特征片段。在本文工作中,统一将这些特征片段命名为时序状态,简称为状态(state)。
将相邻两时序片段之间的状态变化用图(Graph)这种数据结构进行表示。整体时序的演变转化为动态图的变化,形成一种直观可解释的表示用于时序建模与分析。
一个例子
上图展示了一个预测网络流量异常的演化状态图的示例,实线表示写入流量,虚线表示读取流量,红色表示当天发生异常事件。
(a) 可视化了四种识别出的状态,而 (b) 呈现在四个不同的时间间隔(在(a)中标记的I,II,III,IV)的演化状态图。
从图中所示的案例可以看出,当发生异常事件时,状态转换(#2→#16)和(#2→#8)在时间I上更加频繁。
同样,状态转换(#2→#8)和(#8→#23)在时间II处很明显。
这些转变表明,写入和读取流量的不平衡(状态8和状态16)或流量下降(状态23)将导致网络异常。
在时间III,在此期间未发生异常,可以看到状态主要停留在#2中。 然后在下一个时刻IV出现异常。 因此可以看到状态转换#2→#16的明显增加。
2 时序转化的实际案例解析
不仿照论文,下面开始瞎整。。。。。。。。
2.1 步骤一:数据准备与特征提取
就像医生看病,不是只看你一个瞬间的体温,而是会连续观察几分钟,然后总结这几分钟里你的平均体温、最高体温、变化幅度等,形成一个“片段总结”。
这里,我们把连续的服务器数据切分成一个个小片段,然后从每个片段里提取出一些能代表这个片段整体情况的“特征”。
原始数据:
假设我们有如下连续10分钟的服务器数据(每分钟采样一次):
时间点 | CPU (%) | 内存 (%) | 网络延迟 (ms) |
---|---|---|---|
10:00 | 20 | 30 | 50 |
10:01 | 25 | 32 | 55 |
10:02 | 22 | 31 | 52 |
10:03 | 80 | 70 | 150 |
10:04 | 85 | 75 | 160 |
10:05 | 90 | 80 | 170 |
10:06 | 28 | 35 | 60 |
10:07 | 30 | 38 | 62 |
10:08 | 32 | 40 | 65 |
10:09 | 35 | 42 | 68 |
滑动窗口:
我们设定一个滑动窗口大小为3分钟,每1分钟滑动一次。
- 窗口1 (10:00-10:02)
- 窗口2 (10:01-10:03)
- 窗口3 (10:02-10:04)
- …以此类推…
窗口内特征提取:
对每个窗口,我们提取每个指标的平均值和标准差作为特征。
窗口1 (10:00-10:02):
- CPU_avg = (20+25+22)/3 = 22.33
- CPU_std = 标准差(20,25,22) = 2.05
- Mem_avg = (30+32+31)/3 = 31.00
- Mem_std = 标准差(30,32,31) = 0.82
- Latency_avg = (50+55+52)/3 = 52.33
- Latency_std = 标准差(50,55,52) = 2.05
- 窗口1的特征向量:
[22.33, 2.05, 31.00, 0.82, 52.33, 2.05]
窗口2 (10:01-10:03):
- CPU_avg = (25+22+80)/3 = 42.33
- CPU_std = 标准差(25,22,80) = 27.60
- …(类似计算)
- 窗口2的特征向量:
[42.33, 27.60, ..., ...]
(可以看出CPU和内存的标准差会变大,因为有突变)
…以此类推,我们得到一系列代表不同3分钟片段的特征向量。
2.2 步骤二:状态识别与节点构建
现在我们有了一堆“片段总结”(特征向量)。我们发现有些片段总结很相似,比如CPU和内存都很低,网络延迟也正常;
而另一些片段总结则相似,比如CPU和内存都很高,网络延迟也高。我们把这些相似的片段总结归纳成一个个“状态”,这些“状态”就成为了我们图里的**“节点”(Node)**。其中:状态 = 聚类 类别
聚类分析: 我们收集了足够多的历史窗口特征向量后,使用K-Means聚类算法,将这些向量分成K个簇(例如,K=4个状态)。
- 输入: 所有的窗口特征向量(如上面计算的
[22.33, 2.05, ...]
)。 - 操作: K-Means算法会将这些向量分成K类。
- 输出: K个聚类中心,每个中心代表一种“状态”。例如,4个状态可能对应:
- 状态/聚类S0(低负载正常): CPU、内存、延迟均较低。
- 状态/聚类S1(中高负载): CPU、内存、延迟均中等偏高。
- 状态/聚类S2(高负载警告): CPU、内存很高,延迟显著增加。
- 状态/聚类S3(异常抖动): 各指标波动性(标准差)大,可能伴随异常值。
状态映射: 现在,我们把每个原始的时间窗口映射到它所属的“状态”。
- 例如,窗口1的特征向量最接近S0的中心,所以它被映射为状态/聚类S0。
- 窗口2的特征向量可能接近S1的中心(因为它包含了高负载的开始),所以它被映射为状态/聚类S1。
我们得到一个状态/聚类序列:S0 -> S1 -> S2 -> S2 -> S0 -> S0 -> S0 -> S0 (这个序列会比示例长很多)
这些S0, S1, S2, S3就是我们图中的节点。
2.3 步骤三:演化状态图构建
现在我们知道了系统在不同时间点处于什么“状态”。接下来,我们要观察系统是如何从一个状态“跳”到另一个状态的。
比如,是不是经常从“低负载正常”跳到“中高负载”?这些“跳跃”就变成了图里的**“边”(Edge)**,而且我们会记录跳跃的次数,次数越多,边就越“粗”(权重越大)。
构建静态图(基于所有数据):
输入: 状态序列:S0 -> S1 -> S2 -> S2 -> S0 -> S0 -> S0 -> S0
(这个序列会比我们例子中短)
- 操作: 遍历状态序列,统计状态之间的转换频率。
- S0 → \to → S1:发生 X 次
- S1 → \to → S2:发生 Y 次
- S2 → \to → S2:发生 Z 次
- …
- 输出: 一个有向加权图 G s t a t i c G_{static} Gstatic。
- 节点:{S0_LowLoad_Normal, S1_MediumLoad_Stable, S2_HighLoad_Warning, S3_Critical_Anomaly}
- 边:S0 → 权重 = X \xrightarrow{权重=X} 权重=X S1,S1 → 权重 = Y \xrightarrow{权重=Y} 权重=Y S2,等等。
动态图序列生成(模拟):
通俗解释: 最关键的是“动态”!我们不是只构建一个历史的总图,而是每隔一段时间(例如每小时)就基于最新的数据,重新构建一张图。
- 量化例子:
- 图 G t 1 G_{t1} Gt1: 包含了10:00到11:00期间的状态转换关系。
- 图 G t 2 G_{t2} Gt2: 包含了11:00到12:00期间的状态转换关系。
- …
- 优势: 这样我们就能看到系统行为模式是如何随时间变化的。例如,平时S0到S1的边权重很高,但最近几天S1到S2的边权重突然增加了,这可能预示着即将出现问题。
2.4 步骤四:图神经网络(GNN)建模
现在我们有了一系列反映系统演化模式的“关系图谱”。图神经网络就像一个“图谱分析专家”,它能够“读懂”这些图谱:每个状态节点有什么特点?哪些状态之间关系密切?哪些状态的组合预示着危险?通过学习这些规律,它就能预测未来。
图特征提取:
- 真实的GNN会直接处理图结构。这里为了简化,我们从每个
dynamic_graph
中手动提取了一些“图级别”的特征。这些特征模拟了GNN从图结构中学习到的“模式”。例如:- 节点数量、边数量。
- 特定重要状态转换的权重(例如,从“高负载警告”到“异常抖动”的边权重)。
- 某些节点的重要性(例如,PageRank得分,表示该状态在图中的“影响力”)。
- 量化: 对于每个图 G t G_t Gt,我们计算出如
num_nodes
、num_edges
、S2_to_S3_weight
、S3_PageRank
等数值。
训练分类器:
* 输入: 提取的图特征(X_graphs)和对应的事件标签(y_graphs)。
* 模型: 使用 LogisticRegression
模型来模拟GNN的最终预测层。
* 操作: 模型会学习图特征与事件发生之间的关系。
* 输出: 训练好的分类器模型,能够根据图特征预测事件。
2.5 配套代码实践
按照上述步骤,gemini2.5生成的代码
为了简化GNN部分,我们不会实现一个完整的GNN,而是用一个简单的分类器(例如LogisticRegression)来模拟GNN在提取图特征后的预测能力。真实GNN会直接在图结构上操作。
- 第一步:构造数据,模拟服务中断(目标事件):在故障前兆之后不久发生假设在故障前兆结束后的5-10分钟内发生服务中断
- 第二步:滑动窗口与窗口内特征提取,特征归一化(K-Means对尺度敏感),K-Means 聚类识别状态(此处聚类数设置为4)
- 第三步:为每个状态命名,使其更具可解释性 :
- S0: CPU/Mem/Latency 都较低 -> 低负载正常
- S1: CPU/Mem/Latency 都较高 -> 高负载/潜在问题
- S2: CPU/Mem 较高,Latency 偶尔高 -> 中高负载/网络波动
- S3: CPU/Mem/Latency 都在中等水平,可能波动较大 -> 抖动/不稳定
- 第四步:演化状态图构建,用networkx构建静态图构建示例
- 第五步,时间切片批量生成图,其中一个动态图,接下来有很多时间切片的动态图:
第五步:训练简单的基于图特征的分类模型 - 真实GNN会直接处理图结构。这里为了简化,我们从每个图中提取一些“图特征”,提取的特征可以是:节点数量、边数量、特定边的权重、某些节点的重要性(PageRank等)
- 然后用一个传统分类器来模拟GNN的预测能力,设置训练集(如果该图窗口内或之后有事件,则标记分类为
1
)
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from collections import defaultdict
import networkx as nx # 用于图的表示
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score
from sklearn.linear_model import LogisticRegression
# 禁用KMeans的未来警告
import warnings
warnings.filterwarnings("ignore", category=FutureWarning, module="sklearn")
# --- 辅助函数:绘制图 ---
def draw_graph(G, title="Evolutionary State Graph", show_weights=True):
plt.figure(figsize=(10, 8))
pos = nx.spring_layout(G, k=0.5, iterations=50) # 布局算法
node_colors = ['skyblue' if 'S' in node else 'lightcoral' for node in G.nodes()] # 区分节点
nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=2000, alpha=0.9)
nx.draw_networkx_labels(G, pos, font_size=12, font_weight='bold')
if show_weights:
edge_labels = nx.get_edge_attributes(G, 'weight')
nx.draw_networkx_edges(G, pos, width=[0.5+d['weight']*2 for (u,v,d) in G.edges(data=True)], alpha=0.6, edge_color='black')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red', font_size=10)
else:
nx.draw_networkx_edges(G, pos, width=[0.5+ d['weight']*2 for (u,v,d) in G.edges(data=True)], alpha=0.6, edge_color='black')
plt.title(title, fontsize=16)
plt.axis('off')
plt.show()
# --- 阶段一:数据准备与特征提取 ---
print("--- 阶段一:数据准备与特征提取 ---")
# 1. 模拟原始多变量时序数据
np.random.seed(42)
num_minutes = 1000 # 模拟1000分钟的数据
# 正常状态数据
cpu_normal = np.random.uniform(20, 40, num_minutes)
mem_normal = np.random.uniform(30, 50, num_minutes)
latency_normal = np.random.uniform(50, 80, num_minutes)
# 模拟故障前兆数据(高CPU, 高内存, 高延迟)
# 假设在第200-250分钟和第500-580分钟有两个故障事件
fault_start_1 = 200
fault_end_1 = 250
fault_start_2 = 500
fault_end_2 = 580
fault_indices = np.concatenate([np.arange(fault_start_1, fault_end_1), np.arange(fault_start_2, fault_end_2)])
cpu_fault_effect = np.random.uniform(70, 95, len(fault_indices))
mem_fault_effect = np.random.uniform(80, 98, len(fault_indices))
latency_fault_effect = np.random.uniform(150, 300, len(fault_indices))
cpu_data = np.copy(cpu_normal)
mem_data = np.copy(mem_normal)
latency_data = np.copy(latency_normal)
cpu_data[fault_indices] = cpu_fault_effect
mem_data[fault_indices] = mem_fault_effect
latency_data[fault_indices] = latency_fault_effect
# 模拟服务中断(目标事件):在故障前兆之后不久发生
# 假设在故障前兆结束后的5-10分钟内发生服务中断
event_labels = np.zeros(num_minutes, dtype=int)
event_window_size = 10 # 发生事件的时间窗口
for i in range(num_minutes):
if i in fault_indices: # 如果当前是故障前兆
# 查找当前故障前兆结束的索引
fault_end_current = 0
if i >= fault_start_1 and i < fault_end_1:
fault_end_current = fault_end_1 -1
elif i >= fault_start_2 and i < fault_end_2:
fault_end_current = fault_end_2 -1
if i == fault_end_current: # 如果是故障前兆的最后一点
event_occurrence_range = np.arange(fault_end_current + 1, min(num_minutes, fault_end_current + 1 + event_window_size))
event_labels[event_occurrence_range] = 1 # 标记后续窗口为事件发生
time_points = pd.to_datetime(pd.date_range(start='2023-01-01', periods=num_minutes, freq='min'))
df = pd.DataFrame({
'timestamp': time_points,
'CPU': cpu_data,
'Memory': mem_data,
'Latency': latency_data,
'Service_Interruption': event_labels
})
print("原始数据 (前5行):")
print(df.head())
print("\n原始数据 (包含故障前兆的中间部分,以及服务中断标记):")
print(df[195:260])
print(f"\n总共模拟 {num_minutes} 分钟数据,其中 {df['Service_Interruption'].sum()} 分钟标记为服务中断事件。")
# 2. 滑动窗口与窗口内特征提取
window_size = 5 # 每个窗口包含5分钟数据
step_size = 1 # 窗口每次移动1分钟
windowed_features = []
window_start_times = []
window_end_times = []
window_labels = [] # 每个窗口对应的标签
for i in range(0, num_minutes - window_size + 1, step_size):
window_df = df.iloc[i : i + window_size]
# 提取均值和标准差作为特征
features = [
window_df['CPU'].mean(), window_df['CPU'].std(),
window_df['Memory'].mean(), window_df['Memory'].std(),
window_df['Latency'].mean(), window_df['Latency'].std()
]
windowed_features.append(features)
window_start_times.append(window_df['timestamp'].iloc[0])
window_end_times.append(window_df['timestamp'].iloc[-1])
# 窗口的标签:如果窗口期或之后紧接的事件窗口内有任何服务中断,则标记为1
# 这里的标签是对窗口未来事件的预测
future_event_window = df.iloc[i + window_size : min(num_minutes, i + window_size + event_window_size)]
window_labels.append(1 if future_event_window['Service_Interruption'].sum() > 0 else 0)
windowed_features_df = pd.DataFrame(windowed_features, columns=[
'CPU_mean', 'CPU_std', 'Memory_mean', 'Memory_std', 'Latency_mean', 'Latency_std'
])
windowed_features_df['start_time'] = window_start_times
windowed_features_df['end_time'] = window_end_times
windowed_features_df['label'] = window_labels
print(f"\n生成了 {len(windowed_features_df)} 个特征窗口。")
print("特征窗口数据 (前5行):")
print(windowed_features_df.head())
# --- 阶段二:状态识别与节点构建 ---
print("\n--- 阶段二:状态识别与节点构建 ---")
# 1. 特征归一化(K-Means对尺度敏感)
scaler = StandardScaler()
scaled_features = scaler.fit_transform(windowed_features_df.drop(columns=['start_time', 'end_time', 'label']))
# 2. K-Means 聚类识别状态
n_clusters = 4 # 假设我们想识别出4种系统状态
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10) # 明确n_init参数
windowed_features_df['state_id'] = kmeans.fit_predict(scaled_features)
# 获取用于聚类的特征列名,这应该是 scaled_features 对应的原始列名
# 确保这里只包含6个特征列名
feature_columns = ['CPU_mean', 'CPU_std', 'Memory_mean', 'Memory_std', 'Latency_mean', 'Latency_std']
# 打印每个状态的均值特征(聚类中心)
print(f"\n识别出 {n_clusters} 种系统状态。")
print("每个状态的平均特征(反归一化后,方便理解):")
# 修正:使用 feature_columns 作为列名
state_means_scaled = pd.DataFrame(kmeans.cluster_centers_, columns=feature_columns)
state_means = scaler.inverse_transform(state_means_scaled)
state_means_df = pd.DataFrame(state_means, columns=feature_columns, index=range(n_clusters))
# state_means_df['State_Name_ID'] = range(n_clusters)
print(state_means_df)
# 为每个状态命名,使其更具可解释性 (人工判断)
state_names = {}
# 根据state_means_df中的数值判断
# S0: CPU/Mem/Latency 都较低 -> 低负载正常
# S1: CPU/Mem/Latency 都较高 -> 高负载/潜在问题
# S2: CPU/Mem 较高,Latency 偶尔高 -> 中高负载/网络波动
# S3: CPU/Mem/Latency 都在中等水平,可能波动较大 -> 抖动/不稳定
# 实际应根据聚类结果的特征范围来命名,这里是示例
if state_means_df['CPU_mean'].iloc[0] < 50:
state_names[0] = 'S0_LowLoad_Normal'
state_names[1] = 'S1_HighLoad_Warning'
state_names[2] = 'S2_MediumLoad_Stable'
state_names[3] = 'S3_Erratic_Behavior'
else: # 如果聚类结果顺序不同,可能需要调整
state_names = {
state_means_df.sort_values(by='CPU_mean').index[0].split('_')[-1]: 'S0_LowLoad_Normal',
state_means_df.sort_values(by='CPU_mean').index[1].split('_')[-1]: 'S2_MediumLoad_Stable',
state_means_df.sort_values(by='CPU_mean').index[2].split('_')[-1]: 'S3_Erratic_Behavior',
state_means_df.sort_values(by='CPU_mean').index[3].split('_')[-1]: 'S1_HighLoad_Warning',
}
# 重新映射state_id
# id_map = {int(v.split('_')[-1]): v for k, v in state_names.items()}
# reversed_id_map = {v: k for k, v in id_map.items()} # 逆向映射,用于下面真实ID
id_map = state_names
windowed_features_df['state_name'] = windowed_features_df['state_id'].map(id_map)
# state_means_df['State_Name'] = state_means_df.index.map(lambda x: state_names[int(x.split('_')[1])])
state_means_df['State_Name'] = state_means_df.index.map(id_map)
print("\n映射后的状态名称:")
print(state_means_df[['State_Name'] + list(state_means_df.columns[:-1])])
print(f"\n前10个特征窗口的状态序列: {list(windowed_features_df['state_name'].head(10))}")
# --- 阶段三:演化状态图构建 ---
print("\n--- 阶段三:演化状态图构建 ---")
# 1. 静态图构建 (基于所有数据)
G_static = nx.DiGraph()
state_transitions = defaultdict(int) # 统计状态转移次数
for i in range(len(windowed_features_df) - 1):
current_state = windowed_features_df.iloc[i]['state_name']
next_state = windowed_features_df.iloc[i+1]['state_name']
state_transitions[(current_state, next_state)] += 1
# 添加节点
for state_id in windowed_features_df['state_name'].unique():
G_static.add_node(state_id)
# 添加边及权重
max_weight = 0
for (u, v), count in state_transitions.items():
G_static.add_edge(u, v, weight=count)
if count > max_weight:
max_weight = count
# 归一化权重 (可选,方便可视化和后续处理)
for u, v, d in G_static.edges(data=True):
d['weight'] = round(d['weight'] / max_weight, 2) # 归一化到0-1之间
print("\n构建静态演化状态图...")
print(f"节点: {G_static.nodes}")
print(f"边 (及归一化权重): {G_static.edges(data=True)}")
# 绘制静态图
draw_graph(G_static, title="Static Evolutionary State Graph (Overall Transitions)")
# 2. 动态图序列生成 (模拟)
# 在实际中,我们会定期(例如每小时或每天)根据最新的数据窗口构建新的图
# 这里我们简化为,每隔一段时间取一个子集数据构建图
print("\n--- 模拟动态图序列 ---")
dynamic_graph_interval = 200 # 每隔200个窗口构建一个图
dynamic_graphs = []
graph_labels = [] # 每个图对应的事件标签
for i in range(0, len(windowed_features_df) - dynamic_graph_interval + 1, dynamic_graph_interval):
current_graph_df = windowed_features_df.iloc[i : i + dynamic_graph_interval].copy()
current_graph_label = 1 if current_graph_df['label'].sum() > 0 else 0 # 如果该图窗口内或之后有事件,则标记
G_dynamic = nx.DiGraph()
current_state_transitions = defaultdict(int)
for j in range(len(current_graph_df) - 1):
u_state = current_graph_df.iloc[j]['state_name']
v_state = current_graph_df.iloc[j+1]['state_name']
current_state_transitions[(u_state, v_state)] += 1
for state_name in current_graph_df['state_name'].unique():
G_dynamic.add_node(state_name)
current_max_weight = 0
for (u, v), count in current_state_transitions.items():
G_dynamic.add_edge(u, v, weight=count)
if count > current_max_weight:
current_max_weight = count
# 归一化权重
if current_max_weight > 0:
for u, v, d in G_dynamic.edges(data=True):
d['weight'] = round(d['weight'] / current_max_weight, 2)
dynamic_graphs.append(G_dynamic)
graph_labels.append(current_graph_label)
print(f"动态图 {len(dynamic_graphs)} (起始时间: {current_graph_df['start_time'].iloc[0]}) 已构建,标签: {current_graph_label}")
# 绘制其中一个动态图
if len(dynamic_graphs) > 1:
draw_graph(dynamic_graphs[1], title=f"Dynamic Evolutionary State Graph Sample (Graph 2), Label: {graph_labels[1]}")
# --- 阶段四:图神经网络建模 (简化模拟) ---
print("\n--- 阶段四:图神经网络建模 (简化模拟) ---")
# 真实GNN会直接处理图结构。这里为了简化,我们从每个图中提取一些“图特征”
# 然后用一个传统分类器来模拟GNN的预测能力。
# 提取的特征可以是:节点数量、边数量、特定边的权重、某些节点的重要性(PageRank等)
graph_features = []
for G in dynamic_graphs:
num_nodes = G.number_of_nodes()
num_edges = G.number_of_edges()
# 示例:提取从高负载状态到异常状态的边权重
# 需要确保这些状态名称在图中存在,否则设置为0
s1_to_s3_weight = G.get_edge_data('S1_HighLoad_Warning', 'S3_Erratic_Behavior')['weight'] if G.has_edge('S1_HighLoad_Warning', 'S3_Erratic_Behavior') else 0
s0_to_s1_weight = G.get_edge_data('S0_LowLoad_Normal', 'S1_HighLoad_Warning')['weight'] if G.has_edge('S0_LowLoad_Normal', 'S1_HighLoad_Warning') else 0
# 尝试计算PageRank (如果图是连接的)
try:
pr = nx.pagerank(G, weight='weight')
s1_pagerank = pr.get('S1_HighLoad_Warning', 0)
s3_pagerank = pr.get('S3_Erratic_Behavior', 0)
except Exception: # 如果图太小或不连通,PageRank可能失败
s1_pagerank = 0
s3_pagerank = 0
graph_features.append([num_nodes, num_edges, s1_to_s3_weight, s0_to_s1_weight, s1_pagerank, s3_pagerank])
X_graphs = pd.DataFrame(graph_features, columns=[
'num_nodes', 'num_edges', 'S1_to_S3_weight', 'S0_to_S1_weight', 'S1_PageRank', 'S3_PageRank'
])
y_graphs = np.array(graph_labels)
print("\n从动态图中提取的特征 (前5行):")
print(X_graphs.head())
print(f"对应标签 (前5个): {y_graphs[:5]}")
# 训练分类器 (模拟GNN的预测层)
X_train, X_test, y_train, y_test = train_test_split(X_graphs, y_graphs, test_size=0.3, random_state=42, stratify=y_graphs)
model = LogisticRegression(random_state=42, solver='liblinear')
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("\n--- 阶段五:事件预测与可解释性 ---")
print("\n分类器(模拟GNN)在测试集上的性能:")
print(f"准确率: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred))
# 可解释性模拟
print("\n--- 可解释性模拟 ---")
# 检查模型学到的特征重要性
feature_importance = pd.DataFrame({
'Feature': X_graphs.columns,
'Importance': model.coef_[0]
}).sort_values(by='Importance', ascending=False)
print("模型特征重要性(哪些图特征对预测最关键):")
print(feature_importance)
# 假设一个新来的图,进行预测并解释
# 模拟一个接近故障的图特征
# 比如,S1到S3的权重很高,说明系统频繁从高负载警告转为异常行为
new_graph_features = np.array([[4, 8, 0.8, 0.3, 0.2, 0.4]]) # 高S1_to_S3_weight, 高S3_PageRank
predicted_label = model.predict(new_graph_features)[0]
predicted_prob = model.predict_proba(new_graph_features)[0][1] # 预测为1的概率
print(f"\n模拟一个新的图特征,预测结果:{'服务中断' if predicted_label == 1 else '正常'}")
print(f"预测服务中断的概率: {predicted_prob:.4f}")
if predicted_label == 1:
print("\n可解释性洞察:")
print("模型发现:")
print(f"- 从 '高负载警告' (S1_HighLoad_Warning) 到 '异常抖动' (S3_Erratic_Behavior) 的状态转移非常频繁 (权重: {new_graph_features[0][2]:.1f}),这在历史数据中是故障的强烈信号。")
print(f"- '异常抖动' (S3_Erratic_Behavior) 状态在当前图中的重要性 (PageRank: {new_graph_features[0][5]:.1f}) 也较高,表明系统可能处于不稳定状态。")
print("建议:立即检查服务器资源(CPU、内存)及网络连接,可能存在过载或瞬时故障。")
else:
print("\n可解释性洞察:")
print("模型发现:当前系统状态转换模式较为平稳,未检测到显著的故障前兆。")