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

Graph Convolutional Networks训练准确率持续为0%的问题排查求助

指纹分类GCN训练准确率接近0%的排查求助

训练用于指纹分类任务的Graph Convolutional Networks(GCN)时,准确率几乎始终为0%,特此寻求排查帮助。

数据集包含100人的指纹特征数据,每人对应8个指纹样本,单条数据示例如下:

160,56,3,0.484832178465376
200,68,1,0.562318238080993
39,115,1,5.46065431073352
45,128,1,2.37348378271699
...
231,160,1,0.741114780012023
160,163,3,3.93182414892249
127,167,3,5.59876615916843
72,169,1,5.40727184206776

图构建代码

import os
import numpy as np
import networkx as nx
import torch

path = "/kaggle/input/Datasets"
labels = []
adjacency_matrices = []

for person_id in range(1, 101):
    for sample_id in range(1, 9):
        csv_file = os.path.join(path, f"{person_id}_{sample_id}.csv")
        labels.append(person_id - 1)

        minutiae_points = []
        with open(csv_file, 'r') as f:
            for line in f:
                x, y, minutiae_type, angle = line.strip().split(',')
                minutiae_points.append((int(x), int(y)))

        G = nx.Graph()
        G.add_nodes_from(minutiae_points)
        for i, (x1, y1) in enumerate(minutiae_points):
            for j, (x2, y2) in enumerate(minutiae_points):
                if i != j:
                    # Connect nodes based on their proximity
                    if np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) < 50:
                        G.add_edge((x1, y1), (x2, y2))

        adjacency_matrix = nx.adjacency_matrix(G).todense()
        adjacency_matrices.append(torch.from_numpy(adjacency_matrix))

torch.save(adjacency_matrices, 'adjacency_matrices.pt')
torch.save(torch.tensor(labels), 'labels.pt')

训练代码

import os
import numpy as np
import networkx as nx
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.data import Data, DataLoader
from torch_geometric.nn import global_mean_pool
from sklearn.metrics import accuracy_score
from torch.utils.data import random_split

# Create a PyTorch Geometric dataset
fingerprint_dataset = []
num_features = 4  # Number of features for each csv
for i in range(len(adjacency_matrices)):
    edge_index = torch.nonzero(adjacency_matrices[i]).t().contiguous()
    x = torch.ones(edge_index.shape[1], num_features)  # Placeholder for node features
    data = Data(x=x, edge_index=edge_index)
    fingerprint_dataset.append(data)

# Convert labels to a PyTorch Tensor
labels = torch.tensor(labels)

# Split the dataset into train and validation sets
train_dataset, val_dataset = random_split(fingerprint_dataset, [0.8, 0.2])

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)

# Define the GCN model
class FingerprintGCN(torch.nn.Module):
    def __init__(self, in_channels, hidden_channels, num_classes):
        super(FingerprintGCN, self).__init__()
        self.conv1 = GCNConv(in_channels, hidden_channels)
        self.conv2 = GCNConv(hidden_channels, hidden_channels)
        self.conv3 = GCNConv(hidden_channels, hidden_channels)
        self.lin = torch.nn.Linear(hidden_channels, num_classes)

    def forward(self, data):
        x, edge_index, batch = data.x, data.edge_index, data.batch
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, training=self.training)
        x = self.conv2(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, training=self.training)
        x = self.conv3(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, training=self.training)
        x = global_mean_pool(x, batch)  # Average across nodes for each graph
        x = self.lin(x)
        return F.log_softmax(x, dim=1)

# Initialize the model
num_classes = 100  # Number of persons
hidden_channels = 64
model = FingerprintGCN(num_features, hidden_channels, num_classes)

# Train the model
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
criterion = torch.nn.CrossEntropyLoss()
num_epochs = 100

for epoch in range(num_epochs):
    train_loss = 0.0
    val_loss = 0.0
    train_acc = 0.0
    val_acc = 0.0

    # Training loop
    model.train()
    for batch_idx, batch in enumerate(train_loader):
        # Forward pass
        output = model(batch)
        batch_labels = labels[batch_idx * train_loader.batch_size: (batch_idx + 1) * train_loader.batch_size]
        loss = criterion(output, batch_labels.long())

        # Backward pass and optimization
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # Compute training accuracy
        preds = output.argmax(dim=1)
        correct = (preds == batch_labels).sum().item()
        train_acc += correct / len(batch_labels)
        train_loss += loss.item()

    # Validation loop
    model.eval()
    with torch.no_grad():
        for batch_idx, batch in enumerate(val_loader):
            output = model(batch)
            batch_labels = labels[len(train_dataset) + batch_idx * val_loader.batch_size: len(train_dataset) + (batch_idx + 1) * val_loader.batch_size]
            loss = criterion(output, batch_labels.long())

            preds = output.argmax(dim=1)
            correct = (preds == batch_labels).sum().item()
            val_acc += correct / len(batch_labels)
            val_loss += loss.item()

    train_loss /= len(train_loader)
    val_loss /= len(val_loader)
    train_acc /= len(train_loader)
    val_acc /= len(val_loader)

    print(f'Epoch: {epoch+1}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, Train Acc: {train_acc:.4f}, Val Acc: {val_acc:.4f}')

训练输出结果

Epoch: 1, Train Loss: 4.6851, Val Loss: 4.7294, Train Acc: 0.0000, Val Acc: 0.0000
Epoch: 2, Train Loss: 4.5725, Val Loss: 4.8967, Train Acc: 0.0125, Val Acc: 0.0000
Epoch: 3, Train Loss: 4.5426, Val Loss: 5.0557, Train Acc: 0.0125, Val Acc: 0.0000
Epoch: 4, Train Loss: 4.5195, Val Loss: 5.2040, Train Acc: 0.0125, Val Acc: 0.0000
Epoch: 5, Train Loss: 4.5013, Val Loss: 5.3422, Train Acc: 0.0125, Val Acc: 0.0000
...
Epoch: 96, Train Loss: 4.3918, Val Loss: 9.0873, Train Acc: 0.0000, Val Acc: 0.0000
Epoch: 97, Train Loss: 4.3918, Val Loss: 9.1061, Train Acc: 0.0000, Val Acc: 0.0000
Epoch: 98, Train Loss: 4.3917, Val Loss: 9.1249, Train Acc: 0.0000, Val Acc: 0.0000
Epoch: 99, Train Loss: 4.3917, Val Loss: 9.1434, Train Acc: 0.0000, Val Acc: 0.0000
Epoch: 100, Train Loss: 4.3916, Val Loss: 9.1619, Train Acc: 0.0000, Val Acc: 0.0000

排查建议

  • 节点特征完全无效:训练代码中用torch.ones生成的全1特征无法提供任何区分性信息,必须替换为真实的 minutiae 特征。读取csv时要保存每个节点的x、y、minutiae_type、angle数值,转成张量作为节点特征x,示例代码:

    # 读取时保存特征
    minutiae_features = []
    with open(csv_file, 'r') as f:
        for line in f:
            x, y, minutiae_type, angle = line.strip().split(',')
            feat = [float(x), float(y), float(minutiae_type), float(angle)]
            minutiae_features.append(feat)
    # 构建Data时使用真实特征
    x = torch.tensor(minutiae_features, dtype=torch.float)
    
  • 标签匹配错误:random_split打乱了数据集顺序,原labels的连续切片无法对应batch中的样本。正确做法是将标签嵌入每个Data对象:

    # 构建数据集时
    data = Data(x=x, edge_index=edge_index, y=torch.tensor(labels[i], dtype=torch.long))
    # 训练时直接取batch.y
    batch_labels = batch.y
    
  • 图构建与edge_index错误:用(x,y)作为节点ID会导致邻接矩阵索引混乱,建议直接用节点的顺序索引构建图。可以简化图构建逻辑,避免低效的双重循环:

    # 转成numpy数组计算距离
    points = np.array(minutiae_points)
    # 计算所有点对的距离
    dist_matrix = np.sqrt(np.sum((points[:, None] - points[None, :])**2, axis=-1))
    # 筛选距离小于50的边(排除自环)
    edges = np.where((dist_matrix < 50) & (dist_matrix > 0))
    # 构建edge_index
    edge_index = torch.tensor(edges, dtype=torch.long)
    
  • 模型与训练参数优化:

    • 先尝试两层GCN+无Dropout的简单结构,确认特征有效后再增加复杂度,避免过拟合或梯度消失。
    • 将学习率从0.01降至0.001,减少训练震荡。
    • 模型输出无需F.log_softmax,因为CrossEntropyLoss已包含该计算,直接返回self.lin(x)即可。
  • 检查图的连通性:确保每个指纹图没有孤立节点,GCN无法有效学习孤立节点的特征,可通过nx.is_connected(G)检查,若存在孤立节点,可调整距离阈值(比如从50调到60)增加边数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 09:45:55