3,818 次浏览

Morvan博客-tensorflow2学习(三)

前面是关于线性拟合和构建简单神经网络的内容,对于构建过程的可视化,tensorflow提供了一个强大的工具tensorboard。

Tensorboard的使用

tensorflow2与tensorflow1不同使用的动态图,可以说tensorflow中是没有”图”的概念的(reference3)。个人认为对于morvan大佬的自建神经网络,在tensorflow2情况下是十分受阻碍的。

首先使用tf.summary.create_file_writer函数来创建SummaryWriter对象,tensorflow2下使用tensorboard构建神经网络的结构,则还需要使用tf.function装饰器,获得可调用的tensorflow图。然后使用tf.summary.trace_on/tf.summary.trace_export这组函数来进行追踪计算图的信息。

让人头疼的是对于输入的训练数据,以及初始化的参数,这些由于受到动态图的影响只能成为variableop_resource(下图中layer1_w…等名称),动静结合的痛苦,个人也没找到很好的解决方式。

import tensorflow as tf
import numpy as np
from datetime import datetime


def add_layer(inputs, Weights, biases, n_layer, activation_function=None):
    layer_name = 'layer%s' % n_layer
    with tf.name_scope(layer_name):
        with tf.name_scope('Wx_plus_b'):
            Wx_plus_b = tf.matmul(inputs, Weights) + biases
        if activation_function is None:
            outputs = Wx_plus_b
        else:
            outputs = activation_function(Wx_plus_b)
        return outputs


def loss(predicted_y, target_y):
    return tf.math.reduce_mean(tf.math.square(predicted_y - target_y))


def train_one_step(optimizer, x_data, y_data):
    with tf.GradientTape() as tape:
        l1 = add_layer(x_data, Wl, bl, n_layer=1, activation_function=tf.nn.relu)
        prediction = add_layer(l1, Wp, bp, n_layer=2, activation_function=None)
        with tf.name_scope('loss'):
            loss_val = loss(prediction, y_data)
    with tf.name_scope('trains'):
        grads = tape.gradient(loss_val, [Wl, bl, Wp, bp])  # 这里只使用Wl,bl也能得到结果
        optimizer.apply_gradients(zip(grads, [Wl, bl, Wp, bp]))
    return loss_val


@tf.function
def train(optimizer, x_data, y_data):

    loss_val = train_one_step(optimizer, x_data, y_data)

    with summary_writer.as_default():
        tf.summary.scalar('loss', loss_val, step=0)

    return loss_val


x_data = np.linspace(-1, 1, 300)[:, np.newaxis]
noise = np.random.normal(0, 0.05, x_data.shape)
y_data = np.square(x_data) - 0.5 + noise

x_data = x_data.astype(np.float32)
y_data = y_data.astype(np.float32)


Wl = tf.Variable(tf.random.normal((1, 10)), name='Wl')
Wp = tf.Variable(tf.random.normal((10, 1)), name='Wp')
bl = tf.Variable(tf.zeros((1, 10)) + 0.1, name='bl')
bp = tf.Variable(tf.zeros((1, 1)) + 0.1, name='bp')

optimizer = tf.optimizers.SGD(learning_rate=0.1)  # 优化器

stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
logdir = 'logs/%s' % stamp
summary_writer = tf.summary.create_file_writer(logdir)  # 创建file writer

tf.summary.trace_on()
last_loss = train(optimizer, x_data, y_data)
with summary_writer.as_default():
    tf.summary.trace_export(
        name="my_func_trace",
        step=0,
        profiler_outdir=logdir)

keras model的Tensorboard使用

相比于上面的通过,tensorflow对于keras model则做了封装。三句话解决战斗。不同于上面使用函数进行实现神经网络,keras model将参数变量加入到了model.variables中,在使用keras自定义模型时,则可以利用model.variables进行tensorboard的图构建的追踪。

import tensorflow as tf
from tensorflow import keras
import numpy as np
from datetime import datetime


logdir = "logs\\" + datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir)

# 拟合数据的创建
x_data = np.linspace(-1, 1, 300)[:, np.newaxis]
noise = np.random.normal(0, 0.05, x_data.shape)
y_data = np.square(x_data) - 0.5 + noise
x_data = x_data.astype(np.float32)
noise = noise.astype(np.float32)
y_data = y_data.astype(np.float32)
# print(x_data.shape)

# 定义一个一个模型
model = keras.models.Sequential([
    keras.layers.Dense(10, activation='relu'),
    keras.layers.Dense(1, activation='linear')
])

model.compile(
    optimizer=tf.keras.optimizers.SGD(0.01),
    loss='mse')

model.fit(x_data, y_data, epochs=100, callbacks=[tensorboard_callback])

所以在利用keras model构建的神经网络图就很简洁,结构也突出,所以尽量使用keras model来建立自己的神经网络。

关于Lazy loading

首先在Morvan博客-tensorflow2学习(二)改写代码时,稍微阅读了tensorflow的源码,就看到了lazyloader这个类。

在tensorflow1中,需要将构建的静态图加入到session中,进行计算,在session是不推荐使用tf.add的类似操作的,这会导致lazy loading(Lazy loading is a term that refers to a programming pattern when you defer declaring/initializing an object until it is loaded.),虽然不是bug,但是当操作复杂且多的情况下,会导致内存溢出。

x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')
z = tf.add(x, y)
with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
    for _ in range(10):
      sess.run(z)
  writer.close()
x = tf.Variable(10, name='x')
y = tf.Variable(20, name='y')
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
  for _ in range(10):
    sess.run(tf.add(x, y)) # create the op add only when you need to compute it
  writer.close()

正如上面两段代码(reference5),使用print tf.get_default_graph().as_graph_def(),打印出它们的图,你会发现for循环的存在使得lazy loading会存10份add操作,而正常只会存一份add操作。

在改写morvan大佬代码时,对于train过程,train过程是加入了循环来优化参数来减小loss,由于动静结合的问题,使得在tf.function的静态图下存在tf操作,最终在使用tensorboard时,内存跑到3GB溢出(暂时对于修改morvan大佬代码的tensorboard本人不太成功归结于动静结合)。

所以在面对lazy loading问题,首先第一要考虑减少在for循环中减少tf操作,其次需要考虑结构化Tensorflow模型代码,个人也在看如何结构化tensorflow的博文(reference6),最终去尝试解决上面的“动静结合”的问题。

系列:
Morvan博客-tensorflow2学习(一)
Morvan博客-tensorflow2学习(二)
Morvan博客-tensorflow2学习(三)
tensorflow2学习(四)

reference
1. https://morvanzhou.github.io/tutorials/machine-learning/tensorflow/5-16-transfer-learning/
2. https://tensorflow.google.cn/guide
3. https://github.com/tensorflow/tensorboard/issues/1961
4. https://github.com/tensorflow/tensorboard/issues/1929
5. http://web.stanford.edu/class/cs20si/lectures/notes_02.pdf
6. https://danijar.com/structuring-your-tensorflow-models/