首页 > 解决方案 > Tensorflow v1.10+ 为什么在没有检查点的情况下需要输入服务接收器功能?

问题描述

我正在使我的模型适应 TensorFlow 的估算器 API。

我最近根据验证数据提出了一个关于提前停止的问题,除了提前停止之外,还应该导出此时的最佳模型。

看来我对什么是模型导出和什么是检查点的理解并不完整。

检查点是自动建立的。据我了解,检查点足以让估算器开始“温暖” - 使用如此经过训练的权重或错误之前的权重(例如,如果您遇到停电)。

input_fn检查点的好处在于,除了自定义估算器(即和)所需的代码之外,我不必编写任何代码model_fn

虽然,给定一个初始化的估计器,可以调用它的train方法来训练模型,但在实践中,这种方法相当乏味。通常一个人想做几件事:

  1. 定期将网络与验证数据集进行比较,以确保您没有过度拟合
  2. 如果发生过拟合,请尽早停止训练
  3. 每当网络完成时保存最佳模型(通过达到指定数量的训练步骤或通过提前停止标准)。

对于刚接触“高级”估算器 API 的人来说,似乎需要很多低级专业知识(例如对于input_fn),因为如何让估算器做到这一点并不是直截了当的。

通过使用和with可以实现一些轻量级的代码修改#1tf.estimator.TrainSpectf.estimator.EvalSpectf.estimator.train_and_evaluate

在上一个问题中,用户@GPhilo阐明了如何通过使用来自以下方面的半直观函数来实现#2tf.contrib

tf.contrib.estimator.stop_if_no_decrease_hook(my_estimator,'my_metric_to_monitor', 10000)

(不直观为“提前停止不是根据非改进评估的数量触发的,而是根据一定步长范围内非改进评估的数量触发的”)。

@GPhilo - 注意到它与#2无关- 还回答了如何做#3(如原始帖子中所要求的)。然而,我不明白 aninput_serving_fn是什么,为什么需要它,或者如何制作它。

这让我更加困惑,因为不需要这样的函数来创建检查点,或者让估计器从检查点开始“温暖”。

所以我的问题是:

为了帮助回答我的问题,我提供了这个Colab文档。

这个独立的笔记本产生一些虚拟数据,将其保存在 TF Records 中,有一个非常简单的自定义估计器,并使用 TF Record 文件model_fn训练这个模型。input_fn因此,有人向我解释我需要为输入服务接收器功能制作哪些占位符以及如何完成#3就足够了。

更新

@GPhilo最重要的是,我不能低估我对您帮助我(希望其他人)理解此事的周到考虑和关怀的感激之情。

我的“目标”(促使我提出这个问题)是尝试为训练网络构建一个可重用的框架,这样我就可以通过不同的方法build_fn并继续前进(加上具有导出模型的生活质量特征、提前停止等)。

可以在此处找到更新的(根据您的答案)Colab

在阅读了您的答案几次之后,我现在发现了更多的困惑:

1.

您向推理模型提供输入的方式与您用于训练的方式不同

为什么?据我了解,数据输入管道不是:

load raw —> process —> feed to model

反而:

Load raw —> pre process —> store (perhaps as tf records)
# data processing has nothing to do with feeding data to the model?
Load processed —> feed to model

换句话说,我的理解(也许是错误的)是 tf Example/的目的是存储一个完整的单一数据实体,准备就绪——除了从文件中SequenceExample读取之外,不需要其他处理。TFRecord


因此,训练/评估和推理之间可能存在差异input_fn(例如,从文件读取与内存中的渴望/交互式评估),但数据格式是相同的(除了推理,您可能只想提供 1 个示例而不是比一批...)

我同意“<em>输入管道不是模型本身的一部分”。但是,在我看来,而且我的想法显然是错误的,使用估计器,我应该能够为它提供一批进行训练和一个示例(或一批)进行推理。

旁白:“<em>在评估时,您不需要渐变,而是需要不同的输入函数。“,唯一的区别(至少在我的情况下)是您阅读的文件?

  1. 我熟悉那个 TF 指南,但我没有发现它有用,因为我不清楚我需要添加哪些占位符以及需要添加哪些额外的操作来转换数据。

如果我用记录训练我的模型并只想用密集张量进行推理怎么办?

切线地,我在链接指南 subpar 中找到了示例,因为 tf 记录接口要求用户多次定义如何在不同的上下文中从 tf 记录文件中写入/提取特征。此外,鉴于 TF 团队明确表示他们对记录 tf 记录几乎没有兴趣,因此对我来说,任何建立在它之上的文档都同样没有启发性。

  1. 关于tf.estimator.export.build_raw_serving_input_receiver_fn. 占位符叫什么?输入?您能否tf.estimator.export.build_raw_serving_input_receiver_fn通过编写等价物来显示类似物serving_input_receiver_fn

  2. 关于您serving_input_receiver_fn输入图像的示例。你怎么知道调用特征“图像”和接收器张量“输入数据”?那是(后者)标准吗?

  3. 如何使用signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY.

标签: pythontensorflow

解决方案


检查点和导出的最佳模型有什么区别?

检查点至少是一个文件,其中包含在特定时间点获取的特定图形的所有变量的值。通过特定图表,我的意思是当加载你的检查点时,TensorFlow 所做的是遍历你的图表中定义的所有变量(你正在运行的那个),并在检查点文件中搜索一个同名的变量图中的那个。对于恢复训练,这是理想的,因为您的图表在重新启动之间看起来总是一样的。session

导出的模型有不同的用途。导出模型的想法是,一旦你完成了训练,你想要得到一些你可以用于推理的东西,它不包含特定于训练的所有(重)部分(一些例子:梯度计算,全局步进变量,输入管道,...)。此外,他是关键点,通常您向推理模型提供输入的方式与您用于训练的方式不同。对于训练,您有一个输入管道,用于加载、预处理数据并将其提供给您的网络。此输入管道不是模型本身的一部分,可能必须更改以进行推理。这是使用 s 操作时的关键点Estimator

为什么我需要服务输入接收器功能?

为了回答这个问题,我先退后一步。为什么我们需要输入函数?它们是什么?TF Estimators 虽然可能不像其他建模网络的方式那样直观,但有一个很大的优势:它们通过输入函数和模型函数将模型逻辑和输入处理逻辑清楚地分开。

模型分为 3 个不同的阶段:训练、评估和推理。对于最常见的用例(或者至少目前我能想到的),在 TF 中运行的在所有这些阶段都会有所不同。该图是输入预处理、模型和在当前阶段运行模型所需的所有机器的组合。

希望进一步澄清一些示例:训练时,您需要梯度来更新权重、运行训练步骤的优化器、监控事情进展情况的各种指标、从训练集中获取数据的输入管道等. 评估时,你不需要梯度,你需要一个不同的输入函数。当您进行推理时,您所需要的只是模型的前向部分,并且输入函数也会有所不同(没有tf.data.*东西,但通常只是一个占位符)。

s 中的每个阶段Estimator都有自己的输入函数。您熟悉培训和评估的,推理的只是您的serving input receiver功能。在 TF 术语中,“服务”是打包经过训练的模型并将其用于推理的过程(有一个完整的 TensorFlow 服务系统用于大规模操作,但这超出了这个问题,你很可能无论如何都不需要它)。

是时候引用关于该主题的 TF 指南了

在训练期间,input_fn() 会摄取数据并准备好供模型使用。同样,在服务时,aserving_input_receiver_fn() 接受推理请求并为模型准备它们。该功能有以下用途:

  • 将占位符添加到服务系统将提供推理请求的图表中。
  • 添加将数据从输入格式转换为模型预期的特征张量所需的任何其他操作。

现在,服务输入函数规范取决于您计划如何将输入发送到图表。

如果您要将数据打包到(序列化)tf.Example中(类似于 TFRecord 文件中的记录之一),您的服务输入函数将有一个字符串占位符(用于示例的序列化字节)并且将需要说明如何解释示例以提取其数据。如果这是您想要的方式,我邀请您查看上面链接指南中的示例,它实质上显示了您如何设置如何解释示例并解析示例以获取输入数据的规范。

相反,如果您计划直接向网络的第一层提供输入,您仍然需要定义一个服务输入函数,但这次它只包含一个将直接插入网络的占位符。TF 提供了一个可以做到这一点的功能:tf.estimator.export.build_raw_serving_input_receiver_fn.

那么,你真的需要编写自己的输入函数吗?如果您需要的是占位符,则不。只需使用build_raw_serving_input_receiver_fn适当的参数即可。如果您需要更高级的预处理,那么是的,您可能需要自己编写。在这种情况下,它看起来像这样:

def serving_input_receiver_fn():
  """For the sake of the example, let's assume your input to the network will be a 28x28 grayscale image that you'll then preprocess as needed"""
  input_images = tf.placeholder(dtype=tf.uint8,
                                         shape=[None, 28, 28, 1],
                                         name='input_images')
  # here you do all the operations you need on the images before they can be fed to the net (e.g., normalizing, reshaping, etc). Let's assume "images" is the resulting tensor.

  features = {'input_data' : images} # this is the dict that is then passed as "features" parameter to your model_fn
  receiver_tensors = {'input_data': input_images} # As far as I understand this is needed to map the input to a name you can retrieve later
  return tf.estimator.export.ServingInputReceiver(features, receiver_tensors)

我怎样才能训练我的估计器,保存最好的模型,然后再加载它?

model_fn采用mode参数以便有条件地构建模型。例如,在您的 colab 中,您总是有一个优化器。这是错误的,因为它应该只存在于mode == tf.estimator.ModeKeys.TRAIN

其次,您build_fn有一个毫无意义的“输​​出”参数。这个函数应该代表你的推理图,只将你在推理中输入的张量作为输入,并返回 logits/predictions。因此,我将假设outputs参数不存在,因为build_fn签名应该是def build_fn(inputs, params)

此外,您将您定义model_fnfeatures张量。虽然可以这样做,但它既限制了您只有一个输入,又使 serving_fn 的事情复杂化(您不能使用罐头build_raw_...,但需要自己编写并返回 a TensorServingInputReceiver)。我将选择更通用的解决方案并假设您model_fn的解决方案如下(为简洁起见,我省略了变量范围,根据需要添加它):

def model_fn(features, labels, mode, params): 
  my_input = features["input_data"]
  my_input.set_shape(I_SHAPE(params['batch_size']))

  # output of the network
  onet = build_fn(features, params)
  predicted_labels = tf.nn.sigmoid(onet)
  predictions = {'labels': predicted_labels, 'logits': onet}
  export_outputs = { # see EstimatorSpec's docs to understand what this is and why it's necessary.
       'labels': tf.estimator.export.PredictOutput(predicted_labels),
       'logits': tf.estimator.export.PredictOutput(onet) 
  } 
  # NOTE: export_outputs can also be used to save models as "SavedModel"s during evaluation.

  # HERE is where the common part of the graph between training, inference and evaluation stops.
  if mode == tf.estimator.ModeKeys.PREDICT:
    # return early and avoid adding the rest of the graph that has nothing to do with inference.
    return  tf.estimator.EstimatorSpec(mode=mode, 
                                       predictions=predictions, 
                                       export_outputs=export_outputs)

  labels.set_shape(O_SHAPE(params['batch_size']))      

  # calculate loss 
  loss = loss_fn(onet, labels)

  # add optimizer only if we're training
  if mode == tf.estimator.ModeKeys.TRAIN:
    optimizer = tf.train.AdagradOptimizer(learning_rate=params['learning_rate'])
  # some metrics used both in training and eval
  mae = tf.metrics.mean_absolute_error(labels=labels, predictions=predicted_labels, name='mea_op')
  mse = tf.metrics.mean_squared_error(labels=labels, predictions=predicted_labels, name='mse_op')
  metrics = {'mae': mae, 'mse': mse}
  tf.summary.scalar('mae', mae[1])
  tf.summary.scalar('mse', mse[1])

  if mode == tf.estimator.ModeKeys.EVAL:
    return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics, predictions=predictions, export_outputs=export_outputs)

  if mode == tf.estimator.ModeKeys.TRAIN:
    train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op, eval_metric_ops=metrics, predictions=predictions, export_outputs=export_outputs)

现在,在您调用train_and_evaluate完成后设置导出部分:

1)定义您的服务输入功能:

serving_fn = tf.estimator.export.build_raw_serving_input_receiver_fn(
                                       {'input_data':tf.placeholder(tf.float32, [None,#YOUR_INPUT_SHAPE_HERE (without batch size)#])})

2)将模型导出到某个文件夹

est.export_savedmodel('my_directory_for_saved_models', serving_fn)

这会将估算器的当前状态保存到您指定的任何位置。如果您想要一个特定的检查点,请在调用之前加载它export_savedmodel。这将在“my_directory_for_saved_models”中保存一个预测图,其中包含您调用导出函数时估计器所具有的训练参数。

最后,您可能希望冻结图形(查找freeze_graph.py)并优化它以进行推理(查找optimize_for_inference.py和/或transform_graph)获取冻结的*.pb文件,然后您可以根据需要加载并用于推理。


编辑:在更新中添加新问题的答案

边注:

我的“目标”(促使我提出这个问题)是尝试为训练网络构建一个可重用的框架,这样我就可以通过不同的 build_fn 并开始运行(加上具有导出模型的生活质量功能、提前停止等) .

无论如何,如果你成功了,请将它发布在 GitHub 上的某个地方并链接给我。一段时间以来,我一直试图让同样的东西启动并运行一段时间,但结果并不像我希望的那样好。

问题一:

换句话说,我的理解(也许是错误的)是 tf Example / SequenceExample 的目的是存储一个完整的单一数据实体,准备就绪——除了从 TFRecord 文件中读取之外,不需要其他处理。

实际上,通常情况并非如此(尽管理论上您的方式也非常好)。您可以将 TFRecords 视为一种以紧凑的方式存储数据集的(有大量文档的)方式。例如,对于图像数据集,记录通常包含压缩的图像数据(如组成 jpeg/png 文件的字节)、其标签和一些元信息。然后输入管道读取一条记录,对其进行解码,根据需要对其进行预处理并将其提供给网络。当然,您可以在生成 TFRecord 数据集之前移动解码和预处理,并将准备好的数据存储在示例中,但是您的数据集的大小爆炸将是巨大的。

特定的预处理管道是阶段之间变化的一个例子(例如,您可能在训练管道中有数据增强,但在其他管道中没有)。当然,在某些情况下这些管道是相同的,但通常情况并非如此。

关于旁白:

“在评估时,你不需要梯度,你需要一个不同的输入函数。“,唯一的区别(至少在我的情况下)是您阅读的文件?

在你的情况下,可能是。但同样,假设您正在使用数据增强:您需要在 eval 期间禁用它(或者,更好的是,根本没有它),这会改变您的管道。

问题 2:如果我用记录训练我的模型并只想用密集张量进行推理怎么办?

这正是您将管道与模型分开的原因。该模型将张量作为输入并对其进行操作。无论该张量是占位符还是将其从示例转换为张量的子图的输出,这是属于框架的细节,而不是模型本身。

分裂点是模型输入。该模型期望一个张量(或者,在更一般的情况下,一个name:tensor项目的字典)作为输入,并使用它来构建它的计算图。输入的来源由输入函数决定,但只要所有输入函数的输出具有相同的接口,就可以根据需要交换输入,模型将简单地获取并使用它。

因此,回顾一下,假设您使用示例进行训练/评估并使用密集张量进行预测,您的训练和评估输入函数将建立一个管道,该管道从某处读取示例,将它们解码为张量并将其返回给模型以用作输入。另一方面,您的预测输入函数只是为模型的每个输入设置一个占位符并将它们返回给模型,因为它假设您将准备好输入网络的数据放入占位符中。

问题 3:

您将占位符作为 的参数传递build_raw_serving_input_receiver_fn,因此您选择其名称:

tf.estimator.export.build_raw_serving_input_receiver_fn(                                               
    {'images':tf.placeholder(tf.float32, [None,28,28,1], name='input_images')})

问题4:

代码有错误(我混淆了两行),dict的键应该是input_data(我修改了上面的代码)。dict 中的键必须是用来从featuresmodel_fn. 第一model_fn行是:

my_input = features["input_data"]

因此关键是'input_data'。根据 中的键receiver_tensor,我仍然不太确定该角色具有什么作用,因此我的建议是尝试设置与键中不同features的名称并检查名称的显示位置。

问题 5:

我不确定我是否理解,我会在澄清后进行编辑


推荐阅读