我正在调整我的模型到TensorFlow的估算器API.
我最近根据验证数据询问了一个关于提前停止的问题,除了早期停止之外,还应该导出此时的最佳模型.
似乎我对模型导出是什么以及检查点是什么的理解并不完整.
检查点自动生成.根据我的理解,检查点足以使估算器开始"温暖" - 在错误之前使用每个训练过的重量或重量(例如,如果您经历了停电).
检查点的好处在于除了自定义估算器(即input_fn和model_fn)所需的代码之外,我不必编写任何代码.
虽然给定一个初始化的估算器,人们可以调用它的train方法来训练模型,但实际上这种方法相当乏味.通常人们想做几件事:
对于那些刚接触"高级"估算器API的人来说,似乎需要许多低级别的专业知识(例如,对于input_fn),如何让估算器做到这一点并不是直截了当的.
通过一些轻量代码,可以通过使用和使用来实现#1的重做.tf.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)
Run Code Online (Sandbox Code Playgroud)
(不直观的是"早期停止不是根据非改进评估的数量触发,而是根据特定步骤范围内的非改善性逃避的数量").
@GPhilo - 注意它与#2无关- 也回答了如何做#3(根据原始帖子的要求).然而,我不明白input_serving_fn它是什么,为什么需要它,或者如何制造它.
这使我更加困惑,因为不需要这样的功能来制作检查点,或者估计器从检查点开始"热".
所以我的问题是:
为了帮助回答我的问题,我提供了这份Colab文件.
这个自包含的笔记本生成一些虚拟数据,将其保存在TF记录中,有一个非常简单的自定义估算器,model_fn并input_fn使用TF记录文件训练此模型.因此,有人向我解释我需要为输入服务接收器功能制作什么占位符以及如何完成#3.
@GPhilo最重要的是我不能低估我对你的帮助,包括帮助我(并希望其他人)理解这件事.
我的"目标"(激励我提出这个问题)是尝试建立一个可重复使用的培训网络框架,这样我就可以通过不同的方式build_fn去(加上具有出口模型的生活质量,提前停止等).
经过几次你的回答,我发现现在有些困惑:
1.
您为推理模型提供输入的方式与您用于培训的方式不同
为什么?据我所知,数据输入管道不是:
load raw —> process —> feed to model
Run Code Online (Sandbox Code Playgroud)
反而:
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
Run Code Online (Sandbox Code Playgroud)
换句话说,我理解(可能是错误的)tf Example/的意思SequenceExample是存储一个完整的单一基准实体 - 除了从TFRecord文件中读取之外,不需要其他处理.
因此,训练/评估input_fn和推理之间可能存在差异(例如,从文件中读取内存中的热切/交互式评估),但数据格式是相同的(除了推断,您可能只想提供1个示例而不是而不是批...)
我同意" 输入管道不是模型本身的一部分 ".然而,在我看来,我在思考这个问题时显然是错误的,通过估算器,我应该能够为它提供一批培训和一个例子(或批处理)进行推理.
旁白:" 评估时,您不需要渐变,您需要不同的输入功能.",唯一的区别(至少在我的情况下)是你读的文件?
如果我用记录训练我的模型并想要仅使用密集张量进行推理怎么办?
切向,我在链接指南subpar中找到了示例,给定tf记录接口要求用户多次定义如何从不同上下文中的tf记录文件写入/提取特征.此外,鉴于TF团队明确表示他们对记录tf记录没什么兴趣,因此在我之上构建的任何文档都同样没有启发性.
关于tf.estimator.export.build_raw_serving_input_receiver_fn.什么是占位符?输入?你能否tf.estimator.export.build_raw_serving_input_receiver_fn通过写出等价物来表现出类比serving_input_receiver_fn
关于serving_input_receiver_fn输入图像的示例.你怎么知道调用功能'图像'和接收器张量'input_data'?是(后者)标准吗?
如何命名导出signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY.
检查点至少是一个文件,其中包含在特定时间点拍摄的特定图形的所有变量的值.通过特定的图表,我的意思是在加载检查点时,TensorFlow所做的是循环遍历图表中定义的所有变量(session您正在运行的变量)并在检查点文件中搜索与该名称相同的变量.图中的那个.对于恢复训练,这是理想的,因为重新启动之间图形总是看起来相同.
导出的模型用于不同的目的.导出模型的想法是,一旦你完成训练,你想得到一些可用于推理的东西,它不包含训练专用的所有(重)部分(例如:梯度计算,全局步变量,输入管道,...).此外,他是关键点,通常您为推理模型提供输入的方式与您用于培训的方式不同.对于培训,您有一个输入管道,可以加载,预处理并将数据提供给您的网络.此输入管道不是模型本身的一部分,可能必须更改以进行推断.这是使用Estimators 操作时的关键点.
要回答这个问题,我会先退后一步.为什么我们需要所有广告的输入功能呢?TF Estimator虽然可能不像其他模拟网络的方式那样直观,但却具有很大的优势:它们通过输入函数和模型函数明确地将模型逻辑和输入处理逻辑分开.
模型分为3个不同阶段:培训,评估和推理.对于最常见的用例(或至少,我现在能够想到的所有用例),TF中运行的图表在所有这些阶段都会有所不同.该图是输入预处理,模型和在当前阶段运行模型所需的所有机器的组合.
希望进一步澄清的一些例子:在训练时,您需要渐变来更新权重,运行训练步骤的优化器,监控事物进展情况的各种指标,从训练集中获取数据的输入管道等等在评估时,您不需要渐变,而您需要不同的输入功能.当您进行推理时,您所需要的只是模型的前向部分,输入功能也将是不同的(没有tf.data.*东西,但通常只是一个占位符).
Estimators中的每个阶段都有自己的输入功能.您熟悉培训和评估,推理一个只是您的serving input receiver功能.在TF术语中,"服务"是打包经过训练的模型并将其用于推理的过程(有一个完整的TensorFlow服务系统用于大规模操作,但这超出了这个问题,你无论如何都很可能不需要它).
在训练期间,input_fn()摄取数据并准备供模型使用.在服务时,类似地,
serving_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)
Run Code Online (Sandbox Code Playgroud)
您model_fn将获取mode参数以便有条件地构建模型.例如,在你的colab中,你总是有一个优化器.这是错误的,因为它只应该存在mode == tf.estimator.ModeKeys.TRAIN.
其次,你build_fn的"输出"参数毫无意义.此函数应代表您的推理图,仅将您在推理中提供给它的张量作为输入并返回logits/predictions.因此,我将假设outputs参数不存在,因为build_fn签名应该是def build_fn(inputs, params).
此外,您可以在定义model_fn采取features的张量.虽然这可以做到,但它既限制你只有一个输入并使serve_fn的事情变得复杂(你不能使用罐头,build_raw_...但需要自己编写并返回一个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)
Run Code Online (Sandbox Code Playgroud)
现在,要在完成调用后设置导出部分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)#])})
Run Code Online (Sandbox Code Playgroud)
2)将模型导出到某个文件夹
est.export_savedmodel('my_directory_for_saved_models', serving_fn)
Run Code Online (Sandbox Code Playgroud)
这会将估算器的当前状态保存到您指定的任何位置.如果需要特定的检查点,请在调用之前加载它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期间禁用它(或者更好,根本没有它),这会改变您的管道.
这正是您将管道与模型分开的原因.该模型将张量作为输入并对其进行操作.无论该张量是占位符还是子图的输出,它将其从示例转换为张量,这是属于框架的细节,而不是模型本身.
分裂点是模型输入.该模型期望张量(或者,在更通用的情况下,name:tensor项目的词典)作为输入并使用它来构建其计算图.输入来自哪里由输入函数决定,但只要所有输入函数的输出具有相同的接口,就可以根据需要交换输入,模型将简单地获取它所获得的任何内容并使用它.
因此,回顾一下,假设您使用示例进行训练/评估并使用密集张量进行预测,您的训练和评估输入函数将建立一个管道,从某处读取示例,将它们解码为张量并将其返回到模型以用作输入.另一方面,您的预测输入函数只是为模型的每个输入设置一个占位符并将它们返回到模型,因为它假设您将把占据数据准备好输入网络.
您将占位符作为参数传递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')})
Run Code Online (Sandbox Code Playgroud)
代码中有一个错误(我混淆了两行),dict的密钥应该是input_data(我修改了上面的代码).在字典的关键必须是您用来检索从张的关键features在你的model_fn.在model_fn第一行是:
my_input = features["input_data"]
Run Code Online (Sandbox Code Playgroud)
因此关键是'input_data'.根据键入receiver_tensor,我仍然不太清楚它的作用是什么,因此我的建议是尝试设置与键不同的名称features并检查名称显示的位置.
我不确定我理解,经过一些澄清后我会编辑它
| 归档时间: |
|
| 查看次数: |
1128 次 |
| 最近记录: |