GraphNets节点分类中sigmoid激活函数使用异常问题咨询
问题
正在学习DeepMind的Graph-Nets-Library,在实现Zachary空手道俱乐部节点分类任务时陷入瓶颈:任务目标是判定每个节点(对应俱乐部成员)效忠于节点0或33中的哪位教练,使用带Linear模块的InteractionNetwork模块进行节点和边更新,在节点更新后添加了sigmoid激活函数,预期输出0(效忠于节点0)或1(效忠于节点33)的离散结果,但实际得到一系列不同的浮点数值。
以下是使用的代码:
from __future__ import absolute_import from __future__ import division from __future__ import print_function import tree from pyvis.network import Network from graph_nets import blocks from graph_nets import graphs from graph_nets import modules from graph_nets import utils_np from graph_nets import utils_tf import matplotlib.pyplot as plt import networkx as nx import numpy as np import sonnet as snt import tensorflow as tf import functools # making GraphsTuple from karate club dataset # get dataset from nx karate_graph = nx.karate_club_graph() karate_graph_tupel = karate_graph # getting node informations # labeling the nodes nodes = [] for i in range(1,34): if i == 1: nodes.append(0) if i == 33: nodes.append(1) else: nodes.append(-1) nodes = np.reshape(nodes, (len(nodes), 1)) nodes_float = tf.cast(nodes, dtype=tf.float64) # getting edge informations # make graph undirected directed_edges = karate_graph.edges undirected_edges = [(u, v) for u, v in directed_edges] + [(v, u) for u, v in directed_edges] karate_graph.edges = undirected_edges edges = [[0.0] for _ in range(karate_graph.number_of_edges()*2)] # getting sender and receiver informations sender = [] receiver = [] for tupel in karate_graph.edges: sender.append(tupel[0]) receiver.append(tupel[1]) # create GraphTuple from received informations data_dict = { "nodes": nodes_float, "edges": edges, "senders": sender, "receivers": receiver } graphs_tuple = utils_np.data_dicts_to_graphs_tuple([data_dict]) graphs_tuple = tree.map_structure(lambda x: tf.constant(x) if x is not None else None, graphs_tuple) # defining graph network graph_network = modules.InteractionNetwork( node_model_fn=lambda: snt.Sequential([snt.Linear(output_size=1), tf.nn.sigmoid]), edge_model_fn=lambda: snt.Sequential([snt.Linear(output_size=1)]) ) # optimizer and loss function optimizer = tf.keras.optimizers.Adam(learning_rate=0.01) loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True) # learning loop for epoch in range(50): with tf.GradientTape() as tape: output_graph = graph_network(graphs_tuple) # Loss for labeled nodes labeled_nodes = [0, 33] labeled_indices = [i for i in labeled_nodes if graphs_tuple.nodes[i] != -1] loss = loss_fn(tf.gather(graphs_tuple.nodes, labeled_indices), tf.gather(output_graph.nodes, labeled_indices)) # calculate gradient gradients = tape.gradient(loss, graph_network.trainable_variables) # apply gradient optimizer.apply_gradients(zip(gradients, graph_network.trainable_variables)) # Loss output print("Epoch %d | Loss: %.4f" % (epoch, loss.numpy())) print(output_graph.nodes) print(output_graph.edges)
输出结果
损失函数输出
Epoch 0 | Loss: 0.6619 Epoch 1 | Loss: 0.6547 Epoch 2 | Loss: 0.6478 Epoch 3 | Loss: 0.6412 Epoch 4 | Loss: 0.6351 Epoch 5 | Loss: 0.6292 Epoch 6 | Loss: 0.6233 Epoch 7 | Loss: 0.6172 Epoch 8 | Loss: 0.6110 Epoch 9 | Loss: 0.6048 Epoch 10 | Loss: 0.5988 Epoch 11 | Loss: 0.5931 Epoch 12 | Loss: 0.5877 Epoch 13 | Loss: 0.5826 Epoch 14 | Loss: 0.5777 Epoch 15 | Loss: 0.5728 Epoch 16 | Loss: 0.5680 Epoch 17 | Loss: 0.5633 Epoch 18 | Loss: 0.5589 Epoch 19 | Loss: 0.5549
节点输出
[[0.09280719] [0.04476126] [0.03025987] [0.13695013] [0.34953291] [0.26353402] [0.26353402] [0.26353402] [0.22878334] [0.47378198] [0.34953291] [0.54787342] [0.44657832] [0.22878334] [0.47378198] [0.47378198] [0.41969087] [0.44657832] [0.47378198] [0.40082739] [0.47378198] [0.44657832] [0.47378198] [0.21003225] [0.32505647] [0.32505647] [0.47378198] [0.28533633] [0.37482885] [0.28533633] [0.28533633] [0.16495933] [0.01520448] [0.83080503]], shape=(34, 1), dtype=float64
解答
1. 关于sigmoid输出的误解
sigmoid激活函数的作用是将输出映射到0-1之间的概率值,表示样本属于某一类的置信度,而非直接输出离散的0/1标签。要得到离散分类结果,需在训练完成后的推理阶段,对输出概率做阈值判断(例如:大于0.5取1,否则取0):
# 推理阶段生成离散标签 predicted_labels = tf.where(output_graph.nodes > 0.5, 1.0, 0.0) print(predicted_labels)
2. 代码中的关键错误修正
(1) 节点标签初始化错误
原代码循环range(1,34)漏掉了节点0,导致节点0的标签被设为-1,训练时仅用节点33的标签做监督,模型学习严重不足。修正如下:
nodes = [] for i in range(34): # 覆盖0-33所有节点 if i == 0: nodes.append(0) elif i == 33: nodes.append(1) else: nodes.append(-1) nodes = np.reshape(nodes, (len(nodes), 1)) nodes_float = tf.cast(nodes, dtype=tf.float64)
(2) 损失函数参数冲突
原代码使用BinaryCrossentropy(from_logits=True),但节点输出已经过sigmoid激活(不是原始logits),会导致损失计算异常。需二选一调整:
- 方案一:保留sigmoid,设置
from_logits=Falseloss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=False) - 方案二:去掉节点模型的sigmoid,用logits计算损失(通常更稳定)
graph_network = modules.InteractionNetwork( node_model_fn=lambda: snt.Linear(output_size=1), # 移除sigmoid edge_model_fn=lambda: snt.Sequential([snt.Linear(output_size=1)]) ) loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True)
(3) 无向图构建错误
原代码直接修改karate_graph.edges的方式不符合NetworkX规范,会导致边重复计算。正确生成无向图的方式:
# 直接生成无向图,无需手动复制边 karate_graph = nx.karate_club_graph().to_undirected() undirected_edges = list(karate_graph.edges) edges = [[0.0] for _ in range(len(undirected_edges))]
(4) 训练轮次不足
仅训练50轮不足以让GNN充分学习节点间的关联信息,建议将训练轮次增加到200-500轮:
for epoch in range(300): # 调整轮次 # ... 训练逻辑不变
内容的提问来源于stack exchange,提问作者NickT2606
相关产品推荐
相关产品推荐

