TensorFlow 1.10+自定义估计器早停与train_and_evaluate

6

假设您正在使用类似于@simlmx的设置,使用验证数据集来训练自定义tf.estimator.Estimator并使用tf.estimator.train_and_evaluate进行训练:

classifier = tf.estimator.Estimator(
    model_fn=model_fn,
    model_dir=model_dir,
    params=params)

train_spec = tf.estimator.TrainSpec(
    input_fn = training_data_input_fn,
)

eval_spec = tf.estimator.EvalSpec(
    input_fn = validation_data_input_fn,
)

tf.estimator.train_and_evaluate(
    classifier,
    train_spec,
    eval_spec
)

通常,人们使用验证数据集来防止过度拟合,当损失在训练数据集中继续改进但在验证数据集中却没有改进时,就会停止训练。
目前,tf.estimator.EvalSpec 允许指定在多少个steps(默认为100)之后评估模型。
如何在不使用 tf.contrib 函数的情况下指定在n * steps次评估损失没有改进后终止训练,并将“最佳”模型/检查点(由验证数据集确定)保存到唯一的文件名(例如best_validation.checkpoint)?

@GPhilo 相似,但不完全一样。目前还不清楚早停(tf.contrib.estimator.stop_if_no_decrease_hook)钩子是否在 EvalSpec 中起作用。 - SumNeuron
我不确定我理解你的评论。EvalSpec仅指定如何进行评估。早期停止钩子根据策略决定在一系列未改进的评估之后停止训练。每个评估周期都将根据您提供的EvalSpec执行,早期停止钩子对特定的评估规范是不可知的,只关心评估周期的结果。 - GPhilo
@GPhilo 我可能错了,但根据我目前对stop_if_no_decrease_hook的理解,参数max_steps_without_decrease(int,给定指标没有下降的最大训练步数)使用的是TrainSpec输入函数而不是EvalSpec输入函数? - SumNeuron
1个回答

15
我现在明白你的困惑了。stop_if_no_decrease_hook 的文档说明(重点标记为我的):

max_steps_without_decrease: int,指定度量标准未下降的训练步骤的最大数量。

eval_dir:如果设置了该参数,则为包含评估指标摘要文件的目录。默认情况下,将使用 estimator.eval_dir()。

然而,查看 hook 的代码(版本 1.11),你会发现:
def stop_if_no_metric_improvement_fn():
    """Returns `True` if metric does not improve within max steps."""

    eval_results = read_eval_metrics(eval_dir) #<<<<<<<<<<<<<<<<<<<<<<<

    best_val = None
    best_val_step = None
    for step, metrics in eval_results.items(): #<<<<<<<<<<<<<<<<<<<<<<<
      if step < min_steps:
        continue
      val = metrics[metric_name]
      if best_val is None or is_lhs_better(val, best_val):
        best_val = val
        best_val_step = step
      if step - best_val_step >= max_steps_without_improvement: #<<<<<
        tf_logging.info(
            'No %s in metric "%s" for %s steps, which is greater than or equal '
            'to max steps (%s) configured for early stopping.',
            increase_or_decrease, metric_name, step - best_val_step,
            max_steps_without_improvement)
        return True
    return False

代码的作用是加载评估结果(使用您的EvalSpec参数生成),并提取与特定评估记录相关联的eval结果和global_step(或任何其他自定义步骤,用于计数)。
这是文档中“训练步骤”部分的来源:早停不是根据非改进评估的数量触发的,而是根据特定步骤范围内的非改进评估的数量触发(在我看来有点违反直觉)。
因此,简要概括一下:是的,早停挂钩使用评估结果决定何时削减训练,但您需要传递要监视的训练步骤的数量,并记住在该步骤数量中将发生多少次评估。
带有数字示例,以便更好地说明:
假设您无限期进行培训,并每1k步进行一次评估。评估运行的具体方式不相关,只要每1k步运行一次,产生我们想要监视的指标即可。
如果将挂钩设置为hook = tf.contrib.estimator.stop_if_no_decrease_hook(my_estimator,'my_metric_to_monitor',10000),则挂钩将考虑在10k步范围内发生的评估。
由于您每1k步运行1个评估,因此这归结为如果连续10个评估没有任何改进,则进行早期停止。如果然后决定以每2k步进行一次重新运行,则挂钩仅考虑连续5个评估而没有改进。

保留最佳模型

首先,重要说明:这与提前停止无关,保留模型副本和一旦性能开始下降就停止训练的问题完全无关。

通过在您的EvalSpec中定义tf.estimator.BestExporter可以很容易地保留最佳模型(摘自链接中的片段):

  serving_input_receiver_fn = ... # define your serving_input_receiver_fn
  exporter = tf.estimator.BestExporter(
      name="best_exporter",
      serving_input_receiver_fn=serving_input_receiver_fn,
      exports_to_keep=5) # this will keep the 5 best checkpoints

  eval_spec = [tf.estimator.EvalSpec(
    input_fn=eval_input_fn,
    steps=100,
    exporters=exporter,
    start_delay_secs=0,
    throttle_secs=5)]

如果你不知道如何定义serving_input_fn可以看这里
这样可以让你保留最好的5个模型,存储为SavedModel(这是目前存储模型的首选方式)。

感谢您的澄清和解释。在这个钩子中,我如何将最佳模型(根据给定的度量标准)导出到特定的检查点? - SumNeuron
1
非常有用,我很感激,然而serving_input_fn的文档以及其他许多文档都不够详细。这个函数会传递什么参数?如果是一个序列示例会怎样?为什么没有默认值?从阅读那些文档和它所返回的内容,我并不清楚我应该在这个函数中编写什么。 - SumNeuron
此外,虽然它与提前停止无关,但它是 OP 的一部分 :) 如果您想深入了解服务功能并提供一个简单的示例(可能在 Google Colab 中),那就太棒了。 - SumNeuron
“传递给函数的是什么?”任何看起来像从您的 input_fn 返回并在模型中用作“特征”的东西。本质上,它获取输入,通过使用具有适当形状的占位符替换您的 input_fn 直接馈送到模型的输入。 “如果它是一个序列示例怎么办?”没有什么特别的,占位符将具有适当的形状(您定义其形状)。 “为什么没有默认值?”有一些函数可以帮助您,但不能有一种适用于所有情况的默认值,因为您的模型的输入形状是未知的。 - GPhilo
要理解这一点,你需要查阅有关在推断时如何使用保存的模型的信息(关键词:估计器导出模型、SavedModel等)。 - GPhilo
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接