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
相关产品推荐
相关产品推荐

