TF入门05-实验过程管理

时间:2022-07-22
本文章向大家介绍TF入门05-实验过程管理,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

主要内容为:

  • Name Scope
  • Variable Scope
  • 权重共享
  • tf.train.Saver
  • tf.summary
  • 控制实验过程的随机性
  • Autodiff(梯度计算)

1. Name Scope命名空间

TensorBoard中Word2Vec模型计算图表示如图,如果模型更复杂,计算图也越来越乱,我们可以使用name scope将相关的结点放到一个组里来方便运算图的理解。tf.name_scope使用如下:

with tf.name_scope(name_of_that_scope):
    # declare op_1
    # declare op_2
    # ... 

在word2vec模型中,我可以把计算图中的结点划分到4个命名空间中:data、embed、loss和optimizer。

with tf.name_scope('data'):
    iterator = dataset.make_initializable_iterator()
    center_words, target_words = iterator.get_next()

with tf.name_scope('embed'):
    embed_matrix = tf.get_variable('embed_matrix', 
                                   shape=[VOCAB_SIZE, EMBED_SIZE],initializer=tf.random_uniform_initializer())
    embed = tf.nn.embedding_lookup(embed_matrix, center_words, name='embedding')

with tf.name_scope('loss'):
    nce_weight = tf.get_variable('nce_weight', 
                                 shape=[VOCAB_SIZE, EMBED_SIZE], initializer=tf.truncated_normal_initializer())
    nce_bias = tf.get_variable('nce_bias', initializer=tf.zeros([VOCAB_SIZE]))

    loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weight, 
                                        biases=nce_bias, 
                                        labels=target_words, 
                                        inputs=embed,                                       num_sampled=NUM_SAMPLED, 
                                num_classes=VOCAB_SIZE), name='loss')

with tf.name_scope('optimizer'):
    optimizer = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)

此时,TensorBoard中计算图如下:

计算图表示更清晰,可以点击“embed“结点查看这个命名空间中包含的ops。

2. 变量空间Variable scope

命名空间和变量空间的区别在于,变量空间有助于变量共享。那为什么需要变量共享呢?

假设我们创建了一个双层神经网络,之后我们想不同的模型输入能共享模型的权重参数。我们先看看正常情况下会发生什么?

# 定义输入
x1 = tf.truncated_normal([200,100], name='x1')
x2 = tf.truncated_normal([200,100], name='x2')
# 模型定义
def two_hidden_layers(x):
    assert x.shape.as_list() == [200, 100]
    w1 = tf.Variable(tf.random_normal([100, 50]), name="h1_weights")
    b1 = tf.Variable(tf.zeros([50]), name="h1_biases")
 	h1 = tf.matmul(x, w1) + b1
    assert h1.shape.as_list() == [200, 50]
    w2 = tf.Variable(tf.random_normal([50, 10]), name="h2_weights")
    b2 = tf.Variable(tf.zeros([10]), name="h2_biases")
    logits = tf.matmul(h1, w2) + b2
    return logits

# 模型调用
logits1 = two_hidden_layers(x1)
logits2 = two_hidden_layers(x2)

在TensorBoard中可视化结果中有两个子图,每个参数有两套。

为了实现权重共享,我们需要使用tf.get_variable()声明变量;每次调用其创建变量时,会先检测对应变量是否存在,如果存在,则重用这个变量(不再创建);否则,创建变量。但是,这样还是不能完成变量共享。如果调用,会抛出异常。

为了避免这个异常,我们需要将所有要用到变量放到VarScope中,并将VarScope设置为可重用的。

def fully_connected(x, output_dim, scope):
    with tf.variable_scope(scope) as scope:
        w = tf.get_variable("weights", [x.shape[1], output_dim], initializer=tf.random_normal_initializer())
        b = tf.get_variable("biases", [output_dim], initializer=tf.constant_initializer(0.0))
        return tf.matmul(x, w) + b

def two_hidden_layers(x):
    h1 = fully_connected(x, 50, 'h1')
    h2 = fully_connected(h1, 10, 'h2')

with tf.variable_scope('two_layers') as scope:
    logits1 = two_hidden_layers(x1)
    scope.reuse_variables() # 变量重用,实现共享
    logits2 = two_hidden_layers(x2)

此时,模型的可视化结果如下:

运算图中只有一套参数,而且输入x1和x2使用的是同一套参数。

计算图集合Graph collections

定义模型时可能把所有的变量放到了不同的运算子图中。TensorFlow的中tf.get_collection可以用于获取特定运算字体中变量。使用方式为:

tf.get_collection(
	key,
    scope=None
)

其中key为集合名,scope为想要获取的变量空间。

默认情况下,所有的变量存储在tf.GraphKeys.GLOBAL_VARIABLES集合中,比如想要获取‘my_scope’空间中的变量,使用:

tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='my_scope')

创建变量时如果trainable=True(默认情况为True)就会自动添加到集合tf.GraphKeys.TRAINABLE_VARIABLES中。当然,也可以使用tf.add_to_collection(name, value)创建内部不是变量的集合,比如可以创建一个初始化集合,然后将所有的初始化ops放到里面。

3. tf.train.Saver()

实验管理主要指的是能保存模型的参数,以便遇到机器奔溃等情况,模型能从之前的保存的参数中继续训练,而不是重新开始。这对于在大数据集上、复杂模型的训练十分有效。此外,对于研究学者而言,实验结果的可重复性是十分重要的,模型构建和训练时经常需要随机化,如参数的随机初始化,样本的随机打乱。如何控制模型的随机性也是需要解决的问题。

为了当模型奔溃时能从特定时间步的参数恢复重新训练,我们需要定期地对模型参数进行保存。tf.train.Saver()类允许我们将计算图的权重变量保存到二进制文件中。

当我们想每隔1000个训练步进行一次参数保存,使用的代码如下:

# 创建saver对象
saver = tf.train.Saver()

with tf.Session() as sess:
    for step in range(training_steps):
        sess.run([optimizer])
        if (step + 1) % 1000 == 0:
            saver.save(sess, 'checkpoint_directory/model_name', global_step=global_step)

在TF中,运算图变量保存的时间步被称为一个checkpoint。模型训练过程中会保存多个checkpoints,我们使用变量global_step来记录模型完成的训练次数。global_step变量以0初始化,同时声明为不可训练,因为我们不要对它进行优化,只是用来记录训练次数(可以当做一个时间)。

global_step = tf.Variable(0, dtype=tf.int32, trainable=False, name='global_step')

为了记录模型的训练次数,我们需要将global_step作为参数传递给优化器,这样优化每执行一次优化就会对global_step进行修改。

optimizer = tf.train.GradientDescentOptimizer(lr).minimize(loss, global_step=global_step)

为了恢复变量,我们可以使用tf.train.restore(sess, save_path),比如从第10000个step保存的checkpoints进行恢复:

saver.restore(sess, 'checkpoints/skip-gram-10000')

只有checkpoint文件是有效的,权重参数才能正常加载。TF中可以使用tf.train.get_checkpoint_state('directory_name')检查文件夹下checkpoints文件的状态信息。

checkpoint文件记录最新的checkpoint,因此,如果可以从checkpoints/checkpoint下找到最新的checkpoint,然后进行权重参数恢复。checkpoints/checkpoint文件内容如下:

添加saver后,word2vec的训练代码如下:

saver = tf.train.Saver()

initial_step = 0
utils.safe_mkdir('checkpoints')

with tf.Session() as sess:
    sess.run(self.iterator.initializer)
    sess.run(tf.global_variables_initializer())

    ckpt = tf.train.get_checkpoint_state(os.path.dirname('checkpoints/checkpoint'))
    if ckpt and ckpt.model_checkpoint_path:
        saver.restore(sess, ckpt.model_checkpoint_path)

    writer = tf.summary.FileWriter('graphs/word2vec' + str(self.lr), sess.graph)

    for index in range(num_train_steps):
        try:
            sess.run(self.optimizer)
            # save the model every 1000 steps
            if (index + 1) % 1000 == 0: 
                saver.save(sess, 'checkpoints/skip-gram', index)
        except tf.errors.OutOfRangeError:
            sess.run(self.iterator.initializer)
            
    writer.close()

checkpoints文件夹下包含:

默认情况下,save.Saver()会存储计算图中所有的变量;此外,我们也可以通过创建saver时通过一个列表/字典来自定义需要保存的变量。

需要注意的是,savers对象仅存储变量,不存储整个计算图,我们仍需要自己创建运算图,之后才能使用saver加载权重参数。

4.tf.summary

TensorBoard提供了一套用于可视化训练过程统计数据的工具。可以用来可视化损失、准确率的变化,展现形式可以是散点图、直方图甚至是图像。我们创建一个命名空间存放所有的summary ops。

需要将这个summary_op放到sess.run()来执行。

接下来需要将统计数据写入到FileWriter对象中,之后再用TensorBoard可视化,

writer.add_summary(summary, global_step=step)

可视化结果如下:

5. 控制随机性

我们可以通过控制随机过程来在实验中得到稳定的结果。TF控制随机性有两种方式:

5.1 op level

设置op的随机种子来控制其随机性。所有的随机tensor在初始化时可以接受一个随机种子参数。eg:

my_var = tf.Variable(tf.truncated_normal(-1.,1.),stddev=0.1,seed=0)

TF的会话用于记录随机状态,每创建一个新的会话都会从随机种子开始重新开始。

在op level的随机性中,每个op拥有自己的随机数种子。

5.2 Graph Level

tf.set_random_seed(seed)

如果不关心运算图中每个op的随机性,只是为了保证当创建另一个图时计算结果能复现(这样其他人就可以在他们的计算图上复现结果)。当前TF的随机种子只会影响当前的默认计算图.

比如,模型a.pyb.py内部代码相同:

分别运行a、b后可以得到相同的计算结果。

6.Autodiff

我们创建的模型中只是构建模型的前向传播过程,反向传播过程由TF来自动实现。

TF使用反向模型来自动微分,它允许你用与cost大致相等的函数来计算梯度。梯度通过向运算图中添加额外的nodes和edges来计算。比如,要计算C对I的梯度,TF首先会查找两个nodes之间的路径;一旦路径确定后,TF会从C开始逐步反向传播到I。反向传播的每一步都会向运算图中添加一个node,之后通过链式法则计算添加到计算图的偏导数。

我们可以使用tf.gradients()来计算偏导数。

tf.gradients(ys,[xs])[xs]表示ys需要对其进行偏导数计算的tensors列表,返回结果为梯度列表。

你可以自己验证计算结果是否正确。

TF可以自动计算梯度,但是我们不能直观地知道使用什么函数,我们也不能判断模型是否梯度消失或梯度爆炸。但我们需要知道梯度值以便判断为什么某个模型能起效另一个模型却不行。

CS 20: Tensorflow for Deep Learning Research

CS20si 第5课: Word2vec和实验管理(上)