在云端ML引擎中部署的重新训练的inception_v3模型始终输出相同的预测

我按照TensorFlow For Poets的代码实验室,使用inception_v3进行迁移学习。它生成了retrained_graph.pb和retrained_labels.txt文件,这些文件可以用于在本地进行预测(运行label_image.py)。

然后,我希望将这个模型部署到云端ML引擎,以便进行在线预测。为此,我需要将retrained_graph.pb导出为SavedModel格式。我通过遵循Google的@rhaertel80的回答这个Python文件中的指示成功完成了这一操作,该文件来自Flowers Cloud ML Engine教程。这是我的代码:

import tensorflow as tffrom tensorflow.contrib import layersfrom tensorflow.python.saved_model import builder as saved_model_builderfrom tensorflow.python.saved_model import signature_constantsfrom tensorflow.python.saved_model import signature_def_utilsfrom tensorflow.python.saved_model import tag_constantsfrom tensorflow.python.saved_model import utils as saved_model_utilsexport_dir = '../tf_files/saved7'retrained_graph = '../tf_files/retrained_graph2.pb'label_count = 5def build_signature(inputs, outputs):    signature_inputs = { key: saved_model_utils.build_tensor_info(tensor) for key, tensor in inputs.items() }    signature_outputs = { key: saved_model_utils.build_tensor_info(tensor) for key, tensor in outputs.items() }    signature_def = signature_def_utils.build_signature_def(        signature_inputs,        signature_outputs,        signature_constants.PREDICT_METHOD_NAME    )    return signature_defclass GraphReferences(object):  def __init__(self):    self.examples = None    self.train = None    self.global_step = None    self.metric_updates = []    self.metric_values = []    self.keys = None    self.predictions = []    self.input_jpeg = Noneclass Model(object):    def __init__(self, label_count):        self.label_count = label_count    def build_image_str_tensor(self):        image_str_tensor = tf.placeholder(tf.string, shape=[None])        def decode_and_resize(image_str_tensor):            return image_str_tensor        image = tf.map_fn(            decode_and_resize,            image_str_tensor,            back_prop=False,            dtype=tf.string        )        return image_str_tensor    def build_prediction_graph(self, g):        tensors = GraphReferences()        tensors.examples = tf.placeholder(tf.string, name='input', shape=(None,))        tensors.input_jpeg = self.build_image_str_tensor()        keys_placeholder = tf.placeholder(tf.string, shape=[None])        inputs = {            'key': keys_placeholder,            'image_bytes': tensors.input_jpeg        }        keys = tf.identity(keys_placeholder)        outputs = {            'key': keys,            'prediction': g.get_tensor_by_name('final_result:0')        }        return inputs, outputs    def export(self, output_dir):        with tf.Session(graph=tf.Graph()) as sess:            with tf.gfile.GFile(retrained_graph, "rb") as f:                graph_def = tf.GraphDef()                graph_def.ParseFromString(f.read())                tf.import_graph_def(graph_def, name="")            g = tf.get_default_graph()            inputs, outputs = self.build_prediction_graph(g)            signature_def = build_signature(inputs=inputs, outputs=outputs)            signature_def_map = {                signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature_def            }            builder = saved_model_builder.SavedModelBuilder(output_dir)            builder.add_meta_graph_and_variables(                sess,                tags=[tag_constants.SERVING],                signature_def_map=signature_def_map            )            builder.save()model = Model(label_count)model.export(export_dir)

这段代码生成了一个saved_model.pb文件,然后我使用它创建了云端ML引擎模型。我可以使用gcloud ml-engine predict --model my_model_name --json-instances request.json从这个模型获取预测,其中request.json的内容是:

{ "key": "0", "image_bytes": { "b64": "jpeg_image_base64_encoded" } }

然而,无论我在请求中编码哪个jpeg,我总是得到完全相同的错误预测:

预测输出

我猜问题在于云端ML预测API如何将base64编码的图像字节传递给inception_v3的输入张量”DecodeJpeg/contents:0″(在前面的代码中的”build_image_str_tensor()”方法)。你有关于如何解决这个问题并使我的本地重新训练模型在云端ML引擎上提供正确预测的线索吗?

(为了澄清,问题不在于retrained_graph.pb,因为它在本地运行时可以正确预测;也不是在request.json,因为相同的请求文件在遵循上述的Flowers Cloud ML Engine教程时没有问题。)


回答:

首先是一个普遍的警告。TensorFlow for Poets代码实验室的编写方式并不非常适合生产环境的服务(部分表现为你必须实施的变通方法)。你通常会导出一个专门用于预测的图形,不包含所有额外的训练操作。因此,虽然我们可以尝试拼凑一些能够工作的东西,但可能需要额外的工作来使这个图形适合生产环境。

你的代码的方法似乎是导入一个图形,添加一些占位符,然后导出结果。这通常是可以的。然而,在问题中显示的代码中,你添加了输入占位符,但实际上并未将它们连接到导入图形中的任何内容。你最终得到一个包含多个不连接子图的图形,类似于(请原谅这个简陋的图表):

image_str_tensor [input=image_bytes] -> <nothing>keys_placeholder [input=key]  -> identity [output=key]inception_subgraph -> final_graph [output=prediction]

通过inception_subgraph,我指的是你导入的所有操作。

因此,image_bytes实际上是一个无操作并被忽略;key被传输;prediction包含运行inception_subgraph的结果;由于它没有使用你传递的输入,因此每次返回相同的结果(虽然我承认我实际上预期会出现错误)。

为了解决这个问题,我们需要将你创建的占位符连接到inception_subgraph中已存在的占位符,以创建一个大致如下所示的图形:

image_str_tensor [input=image_bytes] -> inception_subgraph -> final_graph [output=prediction]keys_placeholder [input=key]  -> identity [output=key]   

请注意,image_str_tensor将是一批图像,正如预测服务所要求的,但inception图形的输入实际上是单个图像。为了简单起见,我们将采用一种hacky的方式来处理这个问题:我们假设我们将一次发送一个图像。如果我们每次请求发送超过一个图像,我们将得到错误。此外,批量预测将永远不会工作。

你需要的主要更改是导入语句,它将我们添加的占位符连接到图形中已存在的输入(你还将看到更改输入形状的代码):

总的来说,我们得到的是类似于以下内容的代码:

import tensorflow as tffrom tensorflow.contrib import layersfrom tensorflow.python.saved_model import builder as saved_model_builderfrom tensorflow.python.saved_model import signature_constantsfrom tensorflow.python.saved_model import signature_def_utilsfrom tensorflow.python.saved_model import tag_constantsfrom tensorflow.python.saved_model import utils as saved_model_utilsexport_dir = '../tf_files/saved7'retrained_graph = '../tf_files/retrained_graph2.pb'label_count = 5class Model(object):    def __init__(self, label_count):        self.label_count = label_count    def build_prediction_graph(self, g):        inputs = {            'key': keys_placeholder,            'image_bytes': tensors.input_jpeg        }        keys = tf.identity(keys_placeholder)        outputs = {            'key': keys,            'prediction': g.get_tensor_by_name('final_result:0')        }        return inputs, outputs    def export(self, output_dir):        with tf.Session(graph=tf.Graph()) as sess:            # 这将是我们的输入,它接受一批输入            image_bytes = tf.placeholder(tf.string, name='input', shape=(None,))            # 强制它为单个输入;如果我们发送批量,将引发错误。            coerced = tf.squeeze(image_bytes)            # 当我们导入图形时,我们将把`coerced`连接到`DecodeJPGInput:0`            input_map = {'DecodeJPGInput:0': coerced}            with tf.gfile.GFile(retrained_graph, "rb") as f:                graph_def = tf.GraphDef()                graph_def.ParseFromString(f.read())                tf.import_graph_def(graph_def, input_map=input_map, name="")            keys_placeholder = tf.placeholder(tf.string, shape=[None])            inputs = {'image_bytes': image_bytes, 'key': keys_placeholder}            keys = tf.identity(keys_placeholder)            outputs = {                'key': keys,                'prediction': tf.get_default_graph().get_tensor_by_name('final_result:0')}                }            tf.simple_save(sess, output_dir, inputs, outputs)model = Model(label_count)model.export(export_dir)

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注