在之前一篇博客中,借助DeepLabv3+的程序详细讲解了PASCAL-VOC2012数据集的读入过程,今天重点讲一下DeepLab中的训练过程模型配置与tensorboard可视化操作。DeepLab的代码模块结构非常清晰,便于对每一部分进行单独分析,而且DeepLab模型是当前图像语义分割领域最好的模型之一,所以我以该模型代码为例,详细叙述它的模型配置与tensorboard可视化操作过程。之前的博客TensorFlow数据集操作(使用slim和tfrecord)已经将数据集整理成为batch队列的形式,接下来需要对它的训练过程进行配置。
在该程序中对整个训练过程的配置主要使用了slim库中的deployment中的model_deploy文件。该文件中定义了一个DeploymentConfig类以及众多与训练过程配置有关的类函数。首先看一下该文件中的使用方法的介绍。可以看到整个方法结构非常清晰层次明确,将整个模型训练过程分为几个小的独立的模块,包括创建config对象,配置训练时的变量,配置输入数据,配置优化方法,定义网络模型以及最后的模型训练。
Usage:
g = tf.Graph() # 定义计算图
# 创建DeploymentConfig类对象config
config = model_deploy.DeploymentConfig(num_clones=2, clone_on_cpu=True)
# 配置并创建global step来存储变量
with tf.device(config.variables_device()):
global_step = slim.create_global_step()
# 配置输入数据
with tf.device(config.inputs_device()):
images, labels = LoadData(...)
inputs_queue = slim.data.prefetch_queue((images, labels))
# 配置优化方法
with tf.device(config.optimizer_device()):
optimizer = tf.train.MomentumOptimizer(FLAGS.learning_rate, FLAGS.momentum)
# 定义网络模型
def model_fn(inputs_queue):
images, labels = inputs_queue.dequeue() # 从队列中出列图像标签数据
predictions = CreateNetwork(images) # 神经网络前向传播
slim.losses.log_loss(predictions, labels) # 定义损失
# 根据网络,输入数据,优化器定义训练模型
model_dp = model_deploy.deploy(config, model_fn, [inputs_queue],
optimizer=optimizer)
# 运行训练过程
slim.learning.train(model_dp.train_op, my_log_dir,
summary_op=model_dp.summary_op)
下面来具体的看一下DeepLab程序中是如何使用的。可以看到该程序主要都是以config对象对整个训练过程进行配置,这正是slim中定义的DeploymentConfig类的作用。整个过程主要包括三步:(1)定义配置类对象config用于配置训练过程;(2)在TensorFlow计算图中对各个部分进行配置,包括输入数据,变量操作,神经网络模型,优化过程。(3)创建计算图运行变量Session,并通过slim.learning.train函数运行计算图,开始训练过程。本文中程序的主要难点在于设置多GPU运行,需要考虑一些相关参数。该程序的模型具体配置过程与其他程序几乎没有区别,都是采用相同的模型配置方法。
注:这段代码仅仅是源DeepLab代码的部分简化版,去掉了不相关的函数调用与模型可视化部分,仅保留模型配置代码。另外形如FLAGS.***的参数都是运行程序时从命令行得到的参数。
tf.logging.set_verbosity(tf.logging.INFO) # 显示INFO及更高级别的日志消息
# 创建DeploymentConfig对象config,这个配置类描述了如何将一个模型部署在
# 多个单机的多个GPU上,在每个单机上,模型将被复制num_clones次
config = model_deploy.DeploymentConfig(
num_clones=FLAGS.num_clones, # 每个单机部署多少个clone(即部署在多少个GPU)
clone_on_cpu=FLAGS.clone_on_cpu, # 如果为True,则单机中的每个clone将被放在CPU中
replica_id=FLAGS.task, # 整数,模型所部署的单机的索引,通常是0
num_replicas=FLAGS.num_replicas, # 使用多少个单机,通常为1,表示单机部署。此时`worker_device`, `num_ps_tasks`和 `ps_device`这几个参数将被忽略。
num_ps_tasks=FLAGS.num_ps_tasks) # 分布式作业使用多少个单机,如果为0表示不使用单机
# 一个btch的数据要被同时平均分配给所有的GPU,此时每个GPU的batch_size
clone_batch_size = FLAGS.train_batch_size // config.num_clones
# 定义网络模型
def _build_deeplab(inputs_queue, outputs_to_num_classes, ignore_label):
... ...
return outputs_to_scales_to_logits
with tf.Graph().as_default() as graph:
# 配置模型变量
with tf.device(config.variables_device()):
global_step = tf.train.get_or_create_global_step() # 创建global-step,参数为计算图graph,如果没有参数则采用默认计算图
# Define the model and create clones.
model_fn = _build_deeplab # 引用模型,模型的定义在下面具体的函数中
model_args = (inputs_queue, {common.OUTPUT_TYPE: dataset.num_classes}, dataset.ignore_label)
clones = model_deploy.create_clones(config, model_fn, args=model_args) # 创建模型并克隆到多个GPU
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, first_clone_scope) # 返回计算图中的first_clone_scope空间中的名字为UPDATE_OPS的张量集合(即返回需要迭代更新的变量)
# 配置模型的输入数据
with tf.device(config.inputs_device()):
inputs_queue = prefetch_queue.prefetch_queue(samples, capacity=128 * config.num_clones) # 将batch数据预存入队列
first_clone_scope = config.clone_scope(0) # 根据索引号0返回创建的克隆的scope名字first_clone_scope
# 配置优化过程
with tf.device(config.optimizer_device()):
learning_rate = train_utils.get_model_learning_rate( # 设定学习率(poly)
FLAGS.learning_policy, FLAGS.base_learning_rate,
FLAGS.learning_rate_decay_step, FLAGS.learning_rate_decay_factor,
FLAGS.training_number_of_steps, FLAGS.learning_power,
FLAGS.slow_start_step, FLAGS.slow_start_learning_rate)
optimizer = tf.train.MomentumOptimizer(learning_rate, FLAGS.momentum) # 设定优化方法(动量随机梯度下降)
startup_delay_steps = FLAGS.task * FLAGS.startup_delay_steps # 这一条我没有看懂
# 配置模型变量
with tf.device(config.variables_device()):
total_loss, grads_and_vars = model_deploy.optimize_clones(clones, optimizer) # 根据clones网络模型和optimize优化方法计算损失和梯度
total_loss = tf.check_numerics(total_loss, 'Loss is inf or nan.') # 检查检查 NaN 和 Inf 值的张量,如果存在这些张量则返回异常信息
# 修正bias和最后一层权值变量的梯度。对于语义分割任务,模型通常是从分类任务中微调而来。
# 为了微调模型,我们通常把模型的最后一层变量设置更大的学习率
last_layers = model.get_extra_layer_scopes(FLAGS.last_layers_contain_logits_only)
grad_mult = train_utils.get_model_gradient_multipliers(last_layers, FLAGS.last_layer_gradient_multiplier)
if grad_mult:
grads_and_vars = slim.learning.multiply_gradients(grads_and_vars, grad_mult)
# 创建梯度更新对象
grad_updates = optimizer.apply_gradients(grads_and_vars, global_step=global_step)
update_ops.append(grad_updates)
update_op = tf.group(*update_ops)
# 用来控制计算流图,给图中的某些计算指定顺序,即先运行update_op再运行total_loss,即更新完变量后再计算损失
# 与tf.control_dependencies配套使用的tf.identity用于创建一个与原来一样的张量节点到graph中,这样control_dependencies才会生效
with tf.control_dependencies([update_op]):
train_tensor = tf.identity(total_loss, name='train_op')
# 创建session,并对session进行参数配置,指定在GPU设备上的运行情况
session_config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
# 运行训练过程
slim.learning.train(
train_tensor,
logdir=FLAGS.train_logdir,
log_every_n_steps=FLAGS.log_steps,
master=FLAGS.master,
number_of_steps=FLAGS.training_number_of_steps,
is_chief=(FLAGS.task == 0),
session_config=session_config,
startup_delay_steps=startup_delay_steps,
init_fn=train_utils.get_model_init_fn(
FLAGS.train_logdir,
FLAGS.tf_initial_checkpoint,
FLAGS.initialize_last_layer,
last_layers,
ignore_missing_vars=True),
summary_op=summary_op,
save_summaries_secs=FLAGS.save_summaries_secs,
save_interval_secs=FLAGS.save_interval_secs)