TensorFlow 2.0: как сгруппировать график с помощью tf.keras? tf.name_scope / tf.variable_scope больше не используется?

#python #tensorflow #keras #tensorboard #tensorflow2.0

#python #tensorflow #keras #тензорная доска #tensorflow2.0

Вопрос:

Еще в TensorFlow < 2.0 мы определяли слои, особенно более сложные настройки, такие как, например, начальные модули, группируя их с помощью tf.name_scope или tf.variable_scope .

Используя эти операторы, мы смогли удобно структурировать график вычислений, в результате чего представление графика в TensorBoard стало интерпретируемым намного проще.

Только один пример для структурированных групп: введите описание изображения здесь

Это очень удобно для отладки сложных архитектур.

К сожалению, tf.keras кажется, что игнорируется tf.name_scope и tf.variable_scope отсутствует в TensorFlow >= 2.0. Таким образом, решение, подобное этому…

 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. Это простой GAN, реализованный с помощью 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])
  

График в TensorBoard этих моделей выглядит так. Слои представляют собой просто полный беспорядок, а также модели «G» и «D» (с правой стороны) покрывают некоторый беспорядок. «GAN» полностью отсутствует. Не удается правильно открыть обучающую операцию «Adam»: слишком много слоев, нанесенных слева направо, и повсюду стрелки. Очень сложно проверить правильность вашего GAN таким образом.


Хотя реализация TensorFlow 1.X того же GAN охватывает множество «шаблонного кода»…

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

…результирующий график на тензорной панели выглядит значительно чище. Обратите внимание, насколько чисты области «AdamD» и «AdamGAN» в правом верхнем углу. Вы можете напрямую проверить, что ваши оптимизаторы подключены к правильным областям / градиентам.

Комментарии:

1. AFAIK, работает просто использование tf.keras.layers.Conv2D(..., name='conv_1') . Или, пожалуйста, не могли бы вы быть более конкретными?

2. Значит, вам нужно задавать имена вручную, например name='modelX/scopeY/layerZ' ? С tf.variable_scope раньше это было намного проще.

3. На сегодняшний день единственный известный мне вариант — использовать отдельные модели keras.

4. @daniel451 Можете ли вы привести простой пример, когда вы не смогли использовать keras в соответствии с вашими потребностями? Если вы приведете минимальный пример, возможно, я смогу показать, что вам на самом деле не нужна область видимости переменной

5. @mlRocks Теперь я добавил к вопросу простой пример реализации GAN, реализованный как в TF 1.X, так и в TF 2.0 с использованием tf.keras . Пожалуйста, посмотрите на прикрепленные изображения, чтобы сравнить график, созданный TF 2.0, с графиком из TF 1.X с использованием переменных областей.

Ответ №1:

Согласно переменным 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_scope и get_variable для tf 1.0 будут оставлены в tf.compat.v1

Таким образом, вы можете просто вернуться к tf.compat.v1.variable_scope и tf.compat.v1.get_variable , если вам действительно нужно.

Области переменных и tf.get_variable могут быть удобными, но полны мелких подводных камней и угловых случаев, особенно потому, что они ведут себя аналогично, но не совсем так, как области имен, и на самом деле это параллельный механизм для этого. Я думаю, что использование только областей имен будет более последовательным и простым.

Комментарии:

1. Теперь я добавил к вопросу простой пример реализации GAN, реализованный как в TF 1.X, так и в TF 2.0 с использованием tf.keras . Пожалуйста, посмотрите на прикрепленные изображения, чтобы сравнить график, созданный TF 2.0, с графиком из TF 1.X с использованием переменных областей.

2. @daniel451 Вы пробовали поместить код генератора от G_inputs = ... до непосредственно перед G_model = ... внутри with tf.name_scope('G'): ? И аналогично для дискриминатора.

3. Да, я пробовал это: никакой разницы. Кажется, что слои, созданные tf.keras.layers полностью игнорируются tf.name_scope .

4. @daniel451 вы также открыли проблему на GitHub TF? Это похоже на ошибку / отсутствующую функцию в реализации Keras от TF (в конце концов, TF 2.0 все еще альфа-версия ..)

5. Только что обнаружил эту проблему, Tensorflow 2.0 tf.name_scope не влияет на веса, созданные функциональным API keras, созданным 3 дня назад, который, похоже, задает тот же вопрос.