基于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)
现存在两个疑问:
- 上述方法无需额外操作即可学习邻接矩阵权重吗?是否需将边权重单独设为Parameter?若不可行,合适的实现方式是什么?
- 我要进行批量图分类,每个图的节点数可变,能否将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原生支持批量处理,只需遵循以下逻辑:
- 将
feature_transform线性层放在模型类中,所有图共享这套参数——线性变换针对节点特征维度(固定256维),与单张图的节点数无关,权重形状始终为(256,256); - 在模型
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
相关产品推荐
相关产品推荐

