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

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=False
    loss_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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:03:11