GNN解决图着色问题

在tensorflow框架下,模型为GNN模型,要解决图着色(图论)问题,中间大致过程经过编码输入,RNN循环,聚合更新节点,颜色的嵌入,经固定次数嵌入更新,得到最后的节点和颜色的特征,输入MLP,进行预测任务:该图是否接受C中颜色着色?(二分类问题)
我想在最后一层激活函数sigmoid之前加入BN层,部分段代码如下:

    # Get the last embeddings
    last_states = gnn(
      { "M": M_matrix, "VC": VC_matrix, 'chrom_number': chrom_number },
      { "V": vertex_initial_embeddings, "C": colors_initial_embeddings },
      time_steps = time_steps
    )
    GNN["last_states"] = last_states
    V_n = last_states['V'].h
    C_n = last_states['C'].h
    # Compute a vote for each embedding
    V_vote = tf.reshape(V_vote_MLP(V_n), [-1])
    # Add Batch Normalization layer before activation function
    V_vote = tf.keras.layers.BatchNormalization()(V_vote)

    # Compute the number of problems in the batch
    num_problems = tf.shape(n_vertices)[0]
    pred_logits = tf.while_loop(
        lambda i, pred_logits: tf.less(i, num_problems),
        lambda i, pred_logits:
        (
            (i + 1),
            pred_logits.write(
                i,
                tf.reduce_mean(V_vote[tf.reduce_sum(n_vertices[0:i]):tf.reduce_sum(n_vertices[0:i]) + n_vertices[i]])
            )
        ),
        [0, tf.TensorArray(size=num_problems, dtype=tf.float32)]
    )[1].stack()

    # Add sigmoid activation function
    pred_logits = tf.keras.activations.sigmoid(pred_logits)

    # Assign predictions to dictionary
    GNN['predictions'] = pred_logits

出现了如下报错:ValueError: ('Input has undefined axis dimension. Input shape: ', TensorShape([None]))
我应该怎么修改代码呢?
同时我想在如下代码添加梯度裁剪该怎么添加呢?

 # Define loss
    GNN['loss'] = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=cn_exists, logits=pred_logits))

    # Define optimizer
    optimizer = tf.train.AdamOptimizer(name='Adam', learning_rate=learning_rate)

    # Compute cost relative to L2 normalization
    vars_cost = tf.add_n([ tf.nn.l2_loss(var) for var in tf.trainable_variables() ])

    # Define gradients and train step
    grads, _ = tf.clip_by_global_norm(tf.gradients(GNN['loss'] + tf.multiply(vars_cost, l2norm_scaling),tf.trainable_variables()),global_norm_gradient_clipping_ratio)
    GNN['train_step'] = optimizer.apply_gradients(zip(grads, tf.trainable_variables()))
    
    GNN['C_n'] = C_n

要在这段代码中添加梯度裁剪,您需要将 grads 张量传递到 tf.clip_by_global_norm() 函数中,并使用 global_norm_gradient_clipping_ratio 参数指定裁剪比率,例如:


```python
# Define gradients and train step with gradient clipping
grads, _ = tf.clip_by_global_norm(
    tf.gradients(
        GNN['loss'] + tf.multiply(vars_cost, l2norm_scaling),
        tf.trainable_variables()
    ),
    global_norm_gradient_clipping_ratio
)
GNN['train_step'] = optimizer.apply_gradients(zip(grads, tf.trainable_variables()))


```

针对第一个问题,根据错误信息 'Input has undefined axis dimension',推测引起报错的原因是输入张量维度未定义。建议检查在添加 Batch Normalization 层之前的 V_vote 张量的维度是否符合相关要求,这个维度信息应该能够找到该张量或其组成部分的定义位置,例如,V_vote_MLP 或 last_states['V'].h 等。如果确实存在未定义维度的情况,您可以尝试设置明确的维度值或使用 tf.TensorShape([None]) 或类似的方式明确标记该维度。

对于第二个问题,您可以考虑调用tf.clip_by_norm函数,在应用梯度前将所有可训练变量的梯度裁剪到一定的范数阈值以防止梯度爆炸。以下代码演示了如何使用 tf.clip_by_norm 函数在应用梯度前进行梯度裁剪:


# Define loss
GNN['loss'] = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=cn_exists, logits=pred_logits))

# Define optimizer
optimizer = tf.train.AdamOptimizer(name='Adam', learning_rate=learning_rate)

# Compute cost relative to L2 normalization
vars_cost = tf.add_n([tf.nn.l2_loss(var) for var in tf.trainable_variables()])

# Define gradients and apply clipping
grads, _ = tf.clip_by_global_norm(
  tf.gradients(GNN['loss'] + tf.multiply(vars_cost, l2norm_scaling), tf.trainable_variables()),
  global_norm_gradient_clipping_ratio
)

# Apply gradients with optimizer step
GNN['train_step'] = optimizer.apply_gradients(zip(grads, tf.trainable_variables()))

在上面的代码中,我们使用tf.gradients()方法获取可训练变量的梯度,并将其裁剪到指定的阈值 global_norm_gradient_clipping_ratio。然后,您可以应用这些裁剪后的梯度,在优化器的一步中最小化当前损失

引用chatGPT作答,针对第一个问题,错误提示 "Input has undefined axis dimension" 表示输入中某个轴的维度未定义,通常是由于模型输入数据的维度没有设置正确导致的。在这个具体的情况下,可能是因为在 tf.keras.layers.BatchNormalization() 层之前的 V_vote 张量的形状中有一个轴的长度未定义。

一种可能的解决方法是在模型定义的前面添加一个 tf.keras.Input 层,来明确指定输入的形状,例如:

input_shape = (None, num_vertex_features)
V_input = tf.keras.Input(shape=input_shape)

然后将 V_input 作为参数传递到 GNN 模型中,例如:

last_states = gnn(
  { "M": M_matrix, "VC": VC_matrix, 'chrom_number': chrom_number, "V": V_input },
  { "C": colors_initial_embeddings },
  time_steps = time_steps
)

其中 num_vertex_features 可以替换为你实际使用的节点特征的数量。如果你同时有多个特征向量,需要将它们连接成一个更大的向量,然后将连接后的向量作为输入。

关于第二个问题,添加梯度裁剪可以使用 tf.clip_by_global_norm() 函数来实现。具体而言,将梯度张量和裁剪比率传递给 tf.clip_by_global_norm() 函数,它将返回一个裁剪后的梯度张量列表。例如:

grads, _ = tf.clip_by_global_norm(tf.gradients(GNN['loss'] + tf.multiply(vars_cost, l2norm_scaling),tf.trainable_variables()), global_norm_gradient_clipping_ratio)

在这里,global_norm_gradient_clipping_ratio 是裁剪比率。接下来,你可以像之前一样使用 optimizer.apply_gradients() 函数来更新模型参数。

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
针对你提到的问题:

  1. BN层报错:“ValueError: ('Input has undefined axis dimension. Input shape: ', TensorShape([None]))”

这个错误是因为在BN层前的张量V_vote的batch size(第一维)没被指定,是None。依据TensorFlow的规则,无论是网络搭建,还是训练中,所有的张量的每个维度都应该是明确指定好的,否则就会报上述的错误。

解决方法是在构建模型时,为每个张量的第一维指定Batch Size,即在每个张量的shape中第一维加上一个None或具体数值,例如:

input_data = tf.placeholder(tf.float32,shape=[None,784])

如果在模型已经构建好的情况下,想要处理这个问题,可以使用tf.shape()和tf.reshape()两个函数,例如:

V_vote = tf.reshape(V_vote, [tf.shape(V_vote)[0], -1])

其中,tf.shape()返回张量V_vote的形状,[0]表示第一维(batch size),接着使用tf.reshape()重构此张量,其中-1表示让TensorFlow自己计算第二维(自动推导)。

  1. 如何对梯度进行截断(gradient clipping)

有两种方法可以对梯度进行截断。一种方法是手动计算和裁剪梯度。另一种方法是利用TF内置的gradient_clipping函数。

第一种方法的示例代码如下:

grads,_ = tf.clip_by_global_norm(tf.gradients(loss,trainable_vars),max_grad_norm)
train_op = optimizer.apply_gradients(zip(grads,trainable_vars))

这个代码可以在反向传播后,手动计算所有可训练变量的梯度,并进行梯度截断。适用于需要自定义梯度截断方式的情况。

第二种方法的示例代码是这样的:

gvs = optimizer.compute_gradients(loss, var_list=trainable_vars)
capped_gvs = [(tf.clip_by_norm(grad, max_grad_norm), var) for grad, var in gvs]
train_op = optimizer.apply_gradients(capped_gvs)

这个代码使用了optimizer.compute_gradients()函数获得梯度,然后使用tf.clip_by_norm()函数对梯度进行裁剪。这种方式会比较方便,但是可能不太适用于自定义梯度截断的情况。

最后,给出关于BN层报错和梯度裁剪的代码示例,你可以参考这些代码进行修改:

# Add Batch Normalization layer before activation function, with specified batch size
V_vote = tf.expand_dims(V_vote, axis=0)
V_vote = tf.keras.layers.BatchNormalization()(V_vote)
V_vote = tf.reshape(V_vote,[tf.shape(V_vote)[1],-1])

# Define loss
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=cn_exists, logits=pred_logits))

# Define optimizer, apply gradient clipping using TF built-in function
optimizer = tf.train.AdamOptimizer(name='Adam', learning_rate=learning_rate)
trainable_vars = tf.trainable_variables()
gradients = tf.gradients(loss, trainable_vars)
clipped_gradients, global_norm = tf.clip_by_global_norm(gradients, global_norm_gradient_clipping_ratio)
train_op = optimizer.apply_gradients(zip(clipped_gradients, trainable_vars))

希望对你有所帮助!
如果我的回答解决了您的问题,请采纳!

不知道你这个问题是否已经解决, 如果还没有解决的话:
  • 以下回答来自chatgpt:

    对于第一个问题,出现ValueError: ('Input has undefined')的错误,可能是因为BN层无法处理包含NaN或inf的输入。解决方案可能有以下几种:

    1. 检查数据输入是否包含NaN或inf,可以使用tf.debugging.check_numerics函数进行检查和修复。例如:

    python input_data = tf.debugging.check_numerics(input_data, "nan or inf detected in input_data")

    1. 将BN层放在激活函数之前,而不是之后。例如:

    python activation = tf.nn.relu(tf.compat.v1.layers.BatchNormalization()(tf.compat.v1.layers.dense(concat_f1, n_hidden, name='fc1')))

    这样可以保证BN层不会接收到包含NaN和inf的输入。

    1. 可以尝试使用tf.keras.layers.BatchNormalization代替tf.compat.v1.layers.BatchNormalization,因为tf.keras.layers.BatchNormalization在处理NaN和inf时有更好的鲁棒性。

    对于第二个问题,添加梯度裁剪的方式有多种方法,其中一种比较简单的方法如下所示:

    # mlp
    logits = tf.compat.v1.layers.dense(concat_f1, n_out, activation=None, name='fc2')
    predictions = tf.sigmoid(logits)
    
    # loss and optimizer
    loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=labels, logits=logits))
    
    # compute and clip gradients
    grads_and_vars = optimizer.compute_gradients(loss)
    clipped_grads_and_vars = [(tf.clip_by_norm(grad, clip_norm=5.0), var) for grad, var in grads_and_vars]
    
    # apply clipped gradients
    optimizer = tf.compat.v1.train.AdamOptimizer(lr).apply_gradients(clipped_grads_and_vars)
    

    在上述代码中,optimizer.compute_gradients方法获得损失函数关于可训练参数的梯度,然后使用tf.clip_by_norm函数将梯度剪裁到指定范数上,避免梯度爆炸。最后,使用optimizer.apply_gradients方法将剪裁后的梯度应用到可训练参数上。


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^