web-dev-qa-db-fra.com

TensorFlow 2.0: comment grouper un graphe à l'aide de tf.keras? tf.name_scope / tf.variable_scope n'est plus utilisé?

De retour dans TensorFlow <2.0, nous avions l'habitude de définir des couches, en particulier des configurations plus complexes comme modules de création par exemple, en les regroupant avec tf.name_scope ou tf.variable_scope.

En utilisant ces opérateurs, nous avons pu structurer le graphe de calcul de manière pratique, ce qui rend la vue du graphe de TensorBoard plus facilement interprétable.

Un seul exemple pour les groupes structurés: enter image description here

Cela est très pratique pour déboguer des architectures complexes.

Malheureusement, tf.keras semble ignorer tf.name_scope et tf.variable_scope a disparu dans TensorFlow> = 2.0. Ainsi, une solution comme celle-ci ...

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

...n'est plus disponible. Y a-t-il un remplacement?

Comment regrouper des couches et des modèles entiers dans TensorFlow> = 2.0? Si nous ne groupons pas les calques, tf.keras crée un gros gâchis pour les modèles complexes en plaçant simplement tout en série dans la vue graphique.

Y a-t-il un remplacement pour tf.variable_scope? Je n'en ai pas trouvé jusqu'à présent, mais j'ai fait un usage intensif de la méthode dans TensorFlow <2.0.


[~ # ~] modifier [~ # ~] : J'ai maintenant implémenté un exemple pour TensorFlow 2.0 . Il s'agit d'un simple GAN implémenté à l'aide de tf.keras:

# 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])

Le graphique dans TensorBoard de ces modèles ressemble à this . Les couches sont juste un désordre complet et les modèles "G" et "D" (sur le côté droit) couvrent un peu de désordre. "GAN" est complètement manquant. L'opération d'entraînement "Adam" ne peut pas être ouverte correctement: trop de couches juste tracées de gauche à droite et des flèches partout. Très difficile de vérifier l'exactitude de votre GAN de cette façon.


Bien qu'une implémentation TensorFlow 1.X du même GAN couvre beaucoup de "code passe-partout" ...

# 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})

... le résultat graphique TensorBoard semble considérablement plus propre. Remarquez la propreté des portées "AdamD" et "AdamGAN" en haut à droite. Vous pouvez directement vérifier que vos optimiseurs sont attachés aux bonnes étendues/gradients.

16
daniel451

Selon la communauté RFC Variables dans TensorFlow 2. :

  • pour contrôler les noms de variable, les utilisateurs peuvent utiliser tf.name_scope + tf.Variable

En effet, tf.name_scope existe toujours dans TensorFlow 2.0, vous pouvez donc simplement faire:

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"

En outre, comme l'indique le point ci-dessus:

  • la version tf 1.0 de variable_scope et get_variable sera conservée dans tf.compat.v1

Vous pouvez donc simplement revenir à tf.compat.v1.variable_scope et tf.compat.v1.get_variable si vous en avez vraiment besoin.

Portées variables et tf.get_variable peut être pratique mais est truffé de pièges mineurs et de cas d'angle, d'autant plus qu'ils se comportent de manière similaire mais pas exactement comme les étendues de noms, et il s'agit en fait d'un mécanisme parallèle. Je pense que le fait d'avoir simplement des étendues de nom sera plus cohérent et plus simple.

2
jdehesa