You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

图神经网络处理传感器时序批量数据的代码维度报错排查

问题修正与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张量。

错误原因

  1. torch.bmm维度要求:该函数仅接受3D张量输入(格式为[batch_size, N, M]和[batch_size, M, P]),但原代码中adj是2D张量(225×225),support是2D张量(32×64),不满足要求。
  2. GCN输入维度错误:GCN标准输入格式应为[batch_size, num_nodes, in_features],原代码输入是[batch_size, num_nodes](每个节点仅1维特征,但未显式扩展维度)。
  3. 语法错误: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}")

关键修正点说明

  1. 维度调整:
    • 将输入张量从[batch_size, num_nodes]扩展为[batch_size, num_nodes, 1],符合GCN对节点特征维度的要求
    • 将邻接矩阵扩展为[batch_size, num_nodes, num_nodes],满足torch.bmm的3D输入要求
  2. 类定义修正:将GraphConvolution的方法正确嵌套在类中,修复语法错误
  3. 邻接矩阵初始化:新增邻接矩阵的全0初始化,避免未定义变量报错
  4. 罕见异常优化:使用Focal Loss替代普通BCEWithLogitsLoss,解决样本不平衡问题,提升异常样本的检测能力

内容的提问来源于stack exchange,提问作者usama

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 14:55:55