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

基于PyG将邻接矩阵权重作为可学习参数的图分类疑问

问题背景

我正在PyG中针对视频数据集实现Graph CNN,邻接矩阵基于帧中对象变换特征的逆欧氏距离,已实现如下代码:

def calculate_inverse_distances(features):
    in_channel = 256
    linear_layer = nn.Linear(in_channel, in_channel, bias=False)
    linear_layer.to(features.device)
    transformed_features = linear_layer(features)
    transformed_features = transformed_features.detach()
    num_features = transformed_features.shape[0]
    inverse_distances = torch.zeros((num_features, num_features))
    for i in range(num_features):
        for j in range(num_features):
            if i == j:
                continue
            # Calculate the Euclidean distance between feature vectors i and j
            distance = torch.dist(transformed_features[i], transformed_features[j],p=2)
            
            # Avoid division by zero by adding a small epsilon value
            epsilon = 1e-6
            inverse_distances[i, j] = 1.0 / (distance + epsilon)
            inverse_distances[j, i] = inverse_distances[i, j]  # Since distance(i, j) == distance(j, i)
    row_sums = inverse_distances.sum(dim=1, keepdim=True)
    normalized_inverse_distances = inverse_distances / row_sums
    normalized_inverse_distances = F.softmax(inverse_distances, dim=1)
    return inverse_distances

我通过nn.Linear对特征做线性变换,其权重为可学习参数,并用该邻接矩阵创建PyG的Data对象,具体代码如下:

inverse_dists = calculate_inverse_distances(frame_window_features)
edge_index = inverse_dists.nonzero().t()
edge_weight = inverse_dists[edge_index[0], edge_index[1]]
edge_weight.requires_grad = True
data = Data(frame_window_features, edge_index=edge_index, edge_weight = edge_weight, y =labels)

现存在两个疑问:

  1. 上述方法无需额外操作即可学习邻接矩阵权重吗?是否需将边权重单独设为Parameter?若不可行,合适的实现方式是什么?
  2. 我要进行批量图分类,每个图的节点数可变,能否将nn.Linear的可学习参数作为Data对象使用?
解答

问题1:邻接矩阵的可学习性问题

当前实现无法学习邻接矩阵相关权重,核心问题有三点:

  • linear_layer是函数内部临时创建的,每次调用都会初始化新权重,无法累积梯度更新;
  • transformed_features.detach()直接切断了特征到线性层权重的梯度传播路径,梯度无法回传;
  • 返回的inverse_distances未关联线性层计算图,后续设置edge_weight.requires_grad = True无效,梯度链已断裂。

正确实现方式

把线性层作为GNN模型类的一部分,而非临时创建:

class VideoGNN(torch.nn.Module):
    def __init__(self, in_channels=256):
        super().__init__()
        self.feature_transform = nn.Linear(in_channels, in_channels, bias=False)
        # 其他GNN层定义...

    def calculate_inverse_distances(self, features):
        transformed_features = self.feature_transform(features)
        num_nodes = transformed_features.shape[0]
        # 用矩阵运算替代双重循环,提升效率
        dist_matrix = torch.cdist(transformed_features, transformed_features, p=2)
        # 处理对角线元素,避免除零
        dist_matrix = dist_matrix + torch.eye(num_nodes, device=features.device) * 1e-6
        inverse_distances = 1.0 / dist_matrix
        # 对角线设为0,去掉自环
        inverse_distances = inverse_distances * (1 - torch.eye(num_nodes, device=features.device))
        # 行归一化(二选一即可,无需同时做行归一化和softmax)
        row_sums = inverse_distances.sum(dim=1, keepdim=True)
        normalized_inverse_distances = inverse_distances / row_sums
        return normalized_inverse_distances

    def forward(self, data):
        x = data.x
        # 计算邻接矩阵与边权重
        adj_matrix = self.calculate_inverse_distances(x)
        edge_index = adj_matrix.nonzero().t()
        edge_weight = adj_matrix[edge_index[0], edge_index[1]]
        # 后续GNN层计算,例如传入edge_weight到GCNConv
        # ...
  • 无需单独将边权重设为Parameter,边权重由模型的feature_transform层计算而来,梯度会自动回传到线性层权重;
  • 必须去掉detach()调用,保留完整计算图以保证梯度正常传播。

问题2:批量图分类中线性层参数的使用

不能将nn.Linear的可学习参数放到Data对象中。Data对象用于存储单张/批量图的节点特征、边信息、标签等数据,而模型的可学习参数(如线性层权重)属于torch.nn.Module子类,需由模型统一管理才能在训练时更新梯度。

对于节点数可变的批量图分类,PyG原生支持批量处理,只需遵循以下逻辑:

  1. 将feature_transform线性层放在模型类中,所有图共享这套参数——线性变换针对节点特征维度(固定256维),与单张图的节点数无关,权重形状始终为(256,256);
  2. 在模型forward方法中,对批量图的节点特征统一计算邻接矩阵,PyG的Batch对象会自动区分不同图的节点。

批量处理示例:

from torch_geometric.loader import DataLoader

# 假设dataset是包含多个Data对象的列表
dataset = [Data(x=torch.randn(10,256), y=torch.tensor([0])), 
           Data(x=torch.randn(15,256), y=torch.tensor([1])), ...]
loader = DataLoader(dataset, batch_size=4)

model = VideoGNN()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

for batch in loader:
    optimizer.zero_grad()
    out = model(batch)
    loss = F.cross_entropy(out, batch.y)
    loss.backward()
    optimizer.step()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 08:07:45