TensorFlow 2.0: 如何使用tf.keras对图形进行分组?tf.name_scope/tf.variable_scope不再使用了吗?

20

在 TensorFlow < 2.0 中,我们经常使用 tf.name_scope 或者 tf.variable_scope 来定义层,特别是像 inception modules 这样的更复杂的设置。

利用这些操作符,我们能够方便地构建计算图,结果使得 TensorBoard 的图形视图更容易解释。

这里有一个使用结构化分组的例子: enter image description here

这对于调试复杂的架构非常方便。

不幸的是,在 TensorFlow >= 2.0 中,tf.keras 似乎忽略了 tf.name_scope ,并且 tf.variable_scope 已经消失了。因此,像这样的解决方案......

with tf.variable_scope("foo"):
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
        assert v.name == "foo/bar/v:0"

...不再可用。有任何替代品吗?

在TensorFlow >= 2.0中,我们如何对层和整个模型进行分组?如果我们不对层进行分组,tf.keras会在图形视图中将所有内容串行地放置,从而使复杂模型变得混乱。

是否有tf.variable_scope的替代方法?我到目前为止还没有找到任何替代品,但在TensorFlow < 2.0中重度使用了该方法。


编辑:我现在已经实现了一个TensorFlow 2.0的例子。 这是使用tf.keras实现的简单GAN:

# Generator
G_inputs = tk.Input(shape=(100,), name=f"G_inputs")

x = tk.layers.Dense(7 * 7 * 16)(G_inputs)
x = tf.nn.leaky_relu(x)
x = tk.layers.Flatten()(x)
x = tk.layers.Reshape((7, 7, 16))(x)

x = tk.layers.Conv2DTranspose(32, (3, 3), padding="same")(x)
x = tk.layers.BatchNormalization()(x)
x = tf.nn.leaky_relu(x)
x = tf.image.resize(x, (14, 14))

x = tk.layers.Conv2DTranspose(32, (3, 3), padding="same")(x)
x = tk.layers.BatchNormalization()(x)
x = tf.nn.leaky_relu(x)
x = tf.image.resize(x, (28, 28))

x = tk.layers.Conv2DTranspose(32, (3, 3), padding="same")(x)
x = tk.layers.BatchNormalization()(x)
x = tf.nn.leaky_relu(x)

x = tk.layers.Conv2DTranspose(1, (3, 3), padding="same")(x)
x = tf.nn.sigmoid(x)

G_model = tk.Model(inputs=G_inputs,
                   outputs=x,
                   name="G")
G_model.summary()

# Discriminator
D_inputs = tk.Input(shape=(28, 28, 1), name=f"D_inputs")

x = tk.layers.Conv2D(32, (3, 3), padding="same")(D_inputs)
x = tf.nn.leaky_relu(x)
x = tk.layers.MaxPooling2D((2, 2))(x)
x = tk.layers.Conv2D(32, (3, 3), padding="same")(x)
x = tf.nn.leaky_relu(x)
x = tk.layers.MaxPooling2D((2, 2))(x)
x = tk.layers.Conv2D(64, (3, 3), padding="same")(x)
x = tf.nn.leaky_relu(x)

x = tk.layers.Flatten()(x)

x = tk.layers.Dense(128)(x)
x = tf.nn.sigmoid(x)
x = tk.layers.Dense(64)(x)
x = tf.nn.sigmoid(x)
x = tk.layers.Dense(1)(x)
x = tf.nn.sigmoid(x)

D_model = tk.Model(inputs=D_inputs,
                   outputs=x,
                   name="D")

D_model.compile(optimizer=tk.optimizers.Adam(learning_rate=1e-5, beta_1=0.5, name="Adam_D"),
                loss="binary_crossentropy")
D_model.summary()

GAN = tk.Sequential()
GAN.add(G_model)
GAN.add(D_model)
GAN.compile(optimizer=tk.optimizers.Adam(learning_rate=1e-5, beta_1=0.5, name="Adam_GAN"),
            loss="binary_crossentropy")

tb = tk.callbacks.TensorBoard(log_dir="./tb_tf2.0", write_graph=True)

# dummy data
noise = np.random.rand(100, 100).astype(np.float32)
target = np.ones(shape=(100, 1), dtype=np.float32)

GAN.fit(x=noise,
        y=target,
        callbacks=[tb])

这些模型在TensorBoard中的图表看起来像这样。层次结构非常混乱,而且右侧的“G”和“D”模型也很凌乱。“GAN”完全没有显示。训练操作“Adam”无法正确打开:从左到右绘制了太多层,并且箭头到处都是。这种方式很难检查GAN的正确性。


尽管同样的GAN使用TensorFlow 1.X实现涵盖了大量的“样板代码”......

# Generator
Z = tf.placeholder(tf.float32, shape=[None, 100], name="Z")


def model_G(inputs, reuse=False):
    with tf.variable_scope("G", reuse=reuse):
        x = tf.layers.dense(inputs, 7 * 7 * 16)
        x = tf.nn.leaky_relu(x)
        x = tf.reshape(x, (-1, 7, 7, 16))

        x = tf.layers.conv2d_transpose(x, 32, (3, 3), padding="same")
        x = tf.layers.batch_normalization(x)
        x = tf.nn.leaky_relu(x)
        x = tf.image.resize_images(x, (14, 14))

        x = tf.layers.conv2d_transpose(x, 32, (3, 3), padding="same")
        x = tf.layers.batch_normalization(x)
        x = tf.nn.leaky_relu(x)
        x = tf.image.resize_images(x, (28, 28))

        x = tf.layers.conv2d_transpose(x, 32, (3, 3), padding="same")
        x = tf.layers.batch_normalization(x)
        x = tf.nn.leaky_relu(x)

        x = tf.layers.conv2d_transpose(x, 1, (3, 3), padding="same")
        G_logits = x
        G_out = tf.nn.sigmoid(x)

    return G_logits, G_out


# Discriminator
D_in = tf.placeholder(tf.float32, shape=[None, 28, 28, 1], name="D_in")


def model_D(inputs, reuse=False):
    with tf.variable_scope("D", reuse=reuse):
        with tf.variable_scope("conv"):
            x = tf.layers.conv2d(inputs, 32, (3, 3), padding="same")
            x = tf.nn.leaky_relu(x)
            x = tf.layers.max_pooling2d(x, (2, 2), (2, 2))
            x = tf.layers.conv2d(x, 32, (3, 3), padding="same")
            x = tf.nn.leaky_relu(x)
            x = tf.layers.max_pooling2d(x, (2, 2), (2, 2))
            x = tf.layers.conv2d(x, 64, (3, 3), padding="same")
            x = tf.nn.leaky_relu(x)

        with tf.variable_scope("dense"):
            x = tf.reshape(x, (-1, 7 * 7 * 64))

            x = tf.layers.dense(x, 128)
            x = tf.nn.sigmoid(x)
            x = tf.layers.dense(x, 64)
            x = tf.nn.sigmoid(x)
            x = tf.layers.dense(x, 1)
            D_logits = x
            D_out = tf.nn.sigmoid(x)

    return D_logits, D_out

# models
G_logits, G_out = model_G(Z)
D_logits, D_out = model_D(D_in)
GAN_logits, GAN_out = model_D(G_out, reuse=True)

# losses
target = tf.placeholder(tf.float32, shape=[None, 1], name="target")
d_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logits, labels=target))
gan_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=GAN_logits, labels=target))

# train ops
train_d = tf.train.AdamOptimizer(learning_rate=1e-5, name="AdamD") \
    .minimize(d_loss, var_list=tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="D"))
train_gan = tf.train.AdamOptimizer(learning_rate=1e-5, name="AdamGAN") \
    .minimize(gan_loss, var_list=tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope="G"))

# dummy data
dat_noise = np.random.rand(100, 100).astype(np.float32)
dat_target = np.ones(shape=(100, 1), dtype=np.float32)

sess = tf.Session()
tf_init = tf.global_variables_initializer()
sess.run(tf_init)

# merged = tf.summary.merge_all()
writer = tf.summary.FileWriter("./tb_tf1.0", sess.graph)

ret = sess.run([gan_loss, train_gan], feed_dict={Z: dat_noise, target: dat_target})

...最终得到的TensorBoard图看起来更加清晰。请注意右上角的“AdamD”和“AdamGAN”作用域是多么干净。您可以直接检查优化器是否附加到正确的作用域/梯度。


据我所知,只需使用tf.keras.layers.Conv2D(..., name='conv_1')即可。或者您可以更具体地说明一下? - Sharky
1
那么你现在必须手动设置名称,例如 name ='modelX/scopeY/layerZ' 吗?使用 tf.variable_scope 就容易得多了。 - daniel451
1
截至今天,我所知道的唯一选择是使用单独的Keras模型。 - Sharky
@daniel451 你能给一个简单的例子,说明你之前使用keras时无法满足你的需求吗?如果你提供一个最小化的例子,也许我可以向你展示其实你不需要变量作用域。 - enterML
@mlRocks,我现在已经在问题中添加了一个GAN的简单示例实现,使用tf.keras在TF 1.X和TF 2.0中都进行了实现。请查看附加的图像,比较TF 2.0创建的图形与使用变量作用域从TF 1.X创建的图形。 - daniel451
尝试一下并检查是否符合您的需求: img_input = Input(shape=(224,224,3), name='Img_input') x = Conv2D(64, (3,3), activation='relu', name='Conv1')(img_input) x = Flatten(name='flatten')(x) z = Dense(1, activation='sigmoid', name='output')(x) model = Model(img_input, z, name='sample_model') model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) tb = TensorBoard('logs', write_graph=True) model.fit(random_input, random_output, callbacks=[tb], epochs=1, batch_size=8) - enterML
1个回答

3
根据社区RFC TensorFlow 2.0中的变量
  • 用户可以使用tf.name_scope + tf.Variable来控制变量命名
实际上,tf.name_scope 在 TensorFlow 2.0 中仍然存在,所以您可以这样做:
with tf.name_scope("foo"):
    with tf.name_scope("bar"):
        v = tf.Variable([0], dtype=tf.float32, name="v")
        assert v.name == "foo/bar/v:0"

另外,正如上面所述:

  • variable_scopeget_variable的tf 1.0版本将保留在tf.compat.v1中。

因此,如果你确实需要,你可以回退到tf.compat.v1.variable_scopetf.compat.v1.get_variable

变量作用域和tf.get_variable可能很方便,但存在一些小问题和边界情况,特别是因为它们的行为类似但不完全像名称作用域,并且它实际上是名称作用域的并行机制。我认为只使用名称作用域会更加一致和直观。


现在我已经在问题中添加了一个GAN的简单样例实现,使用tf.keras在TF 1.X和TF 2.0中都进行了实现。请参见附加的图片,以比较由TF 2.0创建的图形和使用变量作用域从TF 1.X创建的图形。 - daniel451
@daniel451 你尝试过把从G_inputs = ...G_model = ...的生成器代码放在with tf.name_scope('G'):之内吗?鉴别器也同样。 - jdehesa
是的,我已经尝试过了:没有任何区别。似乎由 tf.keras.layers 创建的层完全忽略了 tf.name_scope - daniel451
@daniel451 你是否也在TF的GitHub上开了一个问题?这似乎是TF的Keras实现中的一个bug/缺失功能(毕竟TF 2.0仍然是alpha版..) - GPhilo
2
刚刚发现了一个问题Tensorflow 2.0 tf.name_scope对由keras functional api创建的权重没有影响,该问题是3天前创建的,似乎问的是同样的问题。 - daniel451
创建权重时,它没有任何效果。代码将提示reuse=True,但没有这样的函数。 - GoingMyWay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接