自定义PyG Data对象训练图自编码器时损失、AUC/AP无变化的解决
问题:自定义PyG Data对象训练Graph Autoencoder无性能变化
我尝试基于自定义PyTorch Geometric(PyG)Data对象训练Graph Autoencoder,但训练全程损失固定为34.5388,AUC和AP始终维持0.5。使用PyG官方示例数据时模型表现正常,因此问题定位在自定义Data对象的创建流程中。
实验采用Geoff Boeing的Aberdeen街道网络节点/边数据,已转换为DataFrame(nodes_ab和edges_ab),初始创建Data对象及训练代码如下:
# Creating node feature tensors node_features = nodes_ab[['x', 'y']].values node_features = torch.tensor(node_features, dtype=torch.float) # Creating edge index edge_index = edges_ab[['source', 'dest']].values.T edge_index = torch.tensor(edge_index, dtype=torch.long) # Create data object data = Data(x=node_features, edge_index=edge_index) # Split data transform = T.RandomLinkSplit(num_val=0.05, num_test=0.1, is_undirected=True, add_negative_train_samples=True) train_data, val_data, test_data = transform(data) # Extract positive and negative edges for train, validation, and test sets def get_pos_neg_edges(data): pos_edge_index = data.edge_label_index[:, data.edge_label == 1] neg_edge_index = data.edge_label_index[:, data.edge_label == 0] return pos_edge_index, neg_edge_index train_pos_edge_index, train_neg_edge_index = get_pos_neg_edges(train_data) val_pos_edge_index, val_neg_edge_index = get_pos_neg_edges(val_data) test_pos_edge_index, test_neg_edge_index = get_pos_neg_edges(test_data) # Add these to the data object data.train_pos_edge_index = train_pos_edge_index data.train_neg_edge_index = train_neg_edge_index data.val_pos_edge_index = val_pos_edge_index data.val_neg_edge_index = val_neg_edge_index data.test_pos_edge_index = test_pos_edge_index data.test_neg_edge_index = test_neg_edge_index # Create encoder and autoencoder class GCNEncoder(torch.nn.Module): def __init__(self, in_channels, out_channels): super(GCNEncoder, self).__init__() self.conv1 = GCNConv(in_channels, 2 * out_channels, cached=True) # cached only for transductive learning self.conv2 = GCNConv(2 * out_channels, out_channels, cached=True) # cached only for transductive learning def forward(self, x, edge_index): x = self.conv1(x, edge_index).relu() return self.conv2(x, edge_index) # parameters out_channels = 2 num_features = data.num_features epochs = 100 # model model = GAE(GCNEncoder(num_features, out_channels)) # move to GPU (if available) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) x = data.x.to(device) train_pos_edge_index = data.train_pos_edge_index.to(device) # inizialize the optimizer optimizer = torch.optim.Adam(model.parameters(), lr=0.03) def train(): model.train() optimizer.zero_grad() z = model.encode(x, train_pos_edge_index) loss = model.recon_loss(z, train_pos_edge_index) loss.backward() optimizer.step() print(f"Training loss: {loss.item()}") return float(loss) def test(pos_edge_index, neg_edge_index): model.eval() with torch.no_grad(): z = model.encode(x, train_pos_edge_index) auc, ap = model.test(z, pos_edge_index, neg_edge_index) return auc, ap # Train the model for epoch in range(1, epochs + 1): loss = train() auc, ap = test(data.test_pos_edge_index, data.test_neg_edge_index) print('Epoch: {:03d}, AUC: {:.4f}, AP: {:.4f}\n _________________________'.format(epoch, auc, ap))
尝试添加节点标签、使用哑变量特征等操作,均未解决问题。
解决步骤
1. 节点索引重编号预处理
原始数据的节点ID可能是非连续的非0起始索引,导致PyG无法正确关联节点特征与边信息,补充索引重编号代码:
# Creating new index for nodes nodes_ab['new_index'] = range(len(nodes_ab)) node_index = nodes_ab[['new_index']].values node_index = torch.tensor(node_index, dtype=torch.float) # Matching 'u' and 'v' with new node index edges_ab = edges_ab.merge(nodes_ab[['osmid', 'new_index']], how='left', left_on='u', right_on='osmid') edges_ab = edges_ab.rename(columns={'new_index': 'new_source'}).drop(columns=['osmid']) edges_ab = edges_ab.merge(nodes_ab[['osmid', 'new_index']], how='left', left_on='v', right_on='osmid') edges_ab = edges_ab.rename(columns={'new_index': 'new_dest'}).drop(columns=['osmid'])
2. 调整数据变换流程
参考PyG官方示例,添加特征归一化和设备转换操作,同时优化RandomLinkSplit的参数配置,确保数据拆分逻辑正确:
# Create transforms transform = T.Compose([ T.NormalizeFeatures(), T.ToDevice(device), T.RandomLinkSplit(num_val=0.05, num_test=0.1, is_undirected=True, split_labels=True, add_negative_train_samples=True), ]) # Create data object data = Data(x=node_features, edge_index=edge_index, edge_attr=edge_features, y=node_index) # Split data train_data, val_data, test_data = transform(data)
3. 修改训练逻辑,直接使用拆分后的数据集
原代码手动提取正负边并挂载到原始data对象,导致训练时未使用拆分后的训练集数据,改为直接调用RandomLinkSplit输出的数据集对象:
def train(train_data): model.train() optimizer.zero_grad() z = model.encode(train_data.x, train_data.edge_index) loss = model.recon_loss(z, train_data.pos_edge_label_index) loss.backward() optimizer.step() print(f"Training loss: {loss.item()}") return float(loss) @torch.no_grad() def test(data): model.eval() z = model.encode(data.x, data.edge_index) return model.test(z, data.pos_edge_label_index, data.neg_edge_label_index) for epoch in range(1, epochs + 1): loss = train(train_data) auc, ap = test(test_data) print('Epoch: {:03d}, AUC: {:.4f}, AP: {:.4f}\n _________________________'.format(epoch, auc, ap))
修改后模型恢复正常,损失逐步下降,AUC和AP指标开始随训练迭代变化。
内容的提问来源于stack exchange,提问作者Ciara Clarke
相关产品推荐
相关产品推荐

