图神经网络处理传感器时序批量数据的代码维度报错排查
问题修正与GNN传感器异常检测实现
问题背景
- 225个传感器组成的网络,时序数据为2022年10月每5分钟采样,单节点共288×31条观测值
- 数据存储:
sensor_data.csv:每行对应一个时间戳的所有传感器数据,共225列(每列对应一个传感器)labels.csv:每行对应同一时间戳的二分类标签(0正常,1异常,异常样本占比极低)
- 已构建225×225邻接矩阵,但GNN代码因维度不匹配报错:调用
forward(self, input, adj)时,input维度为torch.Size([32,225]),计算得到的support为torch.Size([32,64]),执行torch.bmm(adj, support)触发RuntimeError,提示输入需为3D张量。
错误原因
torch.bmm维度要求:该函数仅接受3D张量输入(格式为[batch_size, N, M]和[batch_size, M, P]),但原代码中adj是2D张量(225×225),support是2D张量(32×64),不满足要求。- GCN输入维度错误:GCN标准输入格式应为
[batch_size, num_nodes, in_features],原代码输入是[batch_size, num_nodes](每个节点仅1维特征,但未显式扩展维度)。 - 语法错误:
GraphConvolution类的方法未正确嵌套在类定义中,邻接矩阵未初始化就直接赋值。
修正后的完整代码
import numpy as np import pandas as pd import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset import torch.optim as optim import torch.nn.functional as F # Load data from CSV files sensor_data = pd.read_csv('sensor_data.csv') labels_data = pd.read_csv('labels.csv') n_sensor = 225 # 初始化邻接矩阵为全0 adjacency_matrix = np.zeros((n_sensor, n_sensor), dtype=np.float32) for i in range(n_sensor - 3): adjacency_matrix[i][i + 1] = 1 adjacency_matrix[i][i + 2] = 1 adjacency_matrix[i][i + 3] = 1 adjacency_matrix[i + 1][i] = 1 # 标准化数据 sensor_data_values = sensor_data.iloc[:, 1:].values data_mean = np.mean(sensor_data_values) data_std = np.std(sensor_data_values) sensor_data_values = (sensor_data_values - data_mean) / data_std adjacency_tensor = torch.tensor(adjacency_matrix, dtype=torch.float32) # 补全GraphConvolution类定义 class GraphConvolution(nn.Module): def __init__(self, in_features, out_features): super(GraphConvolution, self).__init__() self.weight = nn.Parameter(torch.FloatTensor(in_features, out_features)) self.bias = nn.Parameter(torch.FloatTensor(out_features)) self.reset_parameters() def reset_parameters(self): nn.init.kaiming_uniform_(self.weight) nn.init.zeros_(self.bias) def forward(self, input, adj): # input维度: [batch_size, num_nodes, in_features] batch_size = input.size(0) # 将邻接矩阵扩展为3D: [batch_size, num_nodes, num_nodes] adj_batch = adj.unsqueeze(0).repeat(batch_size, 1, 1) # 计算支持矩阵: [batch_size, num_nodes, out_features] support = torch.matmul(input, self.weight) # 图卷积运算: adj @ support output = torch.bmm(adj_batch, support) output += self.bias return output class GNN(nn.Module): def __init__(self, num_nodes, in_features=1, hidden_features=64): super(GNN, self).__init__() self.gc1 = GraphConvolution(in_features, hidden_features) self.gc2 = GraphConvolution(hidden_features, 1) # 输出每个节点的二分类logit self.relu = nn.ReLU() def forward(self, x, adj): # 将输入从[batch_size, num_nodes]转为[batch_size, num_nodes, 1] x = x.unsqueeze(-1) x = self.gc1(x, adj) x = self.relu(x) x = self.gc2(x, adj) # 去掉最后一维,转为[batch_size, num_nodes] return x.squeeze(-1) # 针对罕见异常的Focal Loss实现 class FocalLoss(nn.Module): def __init__(self, alpha=0.25, gamma=2.0): super(FocalLoss, self).__init__() self.alpha = alpha self.gamma = gamma def forward(self, inputs, targets): BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction='none') pt = torch.exp(-BCE_loss) F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss return F_loss.mean() # Hyperparameters epochs = 50 learning_rate = 0.001 hidden_features = 64 batch_size = 32 # Model, Loss and Optimizer model = GNN(num_nodes=n_sensor, hidden_features=hidden_features) # 异常样本占比极低,使用Focal Loss替代普通BCE criterion = FocalLoss(alpha=0.9, gamma=2.0) # alpha可根据实际异常占比调整 optimizer = optim.Adam(model.parameters(), lr=learning_rate) # Convert data to tensor inputs = torch.tensor(sensor_data_values, dtype=torch.float32) labels = torch.tensor(labels_data.iloc[:, 1:].values, dtype=torch.float32) # Create data loaders dataset = TensorDataset(inputs, labels) loader = DataLoader(dataset, batch_size=batch_size, shuffle=True) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) adjacency_tensor = adjacency_tensor.to(device) lossHist = [] for epoch in range(epochs): total_loss = 0 model.train() for batch_inputs, batch_labels in loader: batch_inputs, batch_labels = batch_inputs.to(device), batch_labels.to(device) optimizer.zero_grad() outputs = model(batch_inputs, adjacency_tensor) loss = criterion(outputs, batch_labels) loss.backward() optimizer.step() total_loss += loss.item() * batch_inputs.size(0) avg_loss = total_loss / len(dataset) lossHist.append(avg_loss) if epoch % 5 == 0: print(f"Epoch {epoch}, Loss: {avg_loss:.4f}")
关键修正点说明
- 维度调整:
- 将输入张量从
[batch_size, num_nodes]扩展为[batch_size, num_nodes, 1],符合GCN对节点特征维度的要求 - 将邻接矩阵扩展为
[batch_size, num_nodes, num_nodes],满足torch.bmm的3D输入要求
- 将输入张量从
- 类定义修正:将
GraphConvolution的方法正确嵌套在类中,修复语法错误 - 邻接矩阵初始化:新增邻接矩阵的全0初始化,避免未定义变量报错
- 罕见异常优化:使用Focal Loss替代普通BCEWithLogitsLoss,解决样本不平衡问题,提升异常样本的检测能力
内容的提问来源于stack exchange,提问作者usama
相关产品推荐
相关产品推荐

