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

自定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:34:58