在 Cloud ML Engine 中部署的重新训练 inception_v3 模型始终输出相同的预测
Retrained inception_v3 model deployed in Cloud ML Engine always outputs the same predictions
我关注了代码实验室 TensorFlow For Poets for transfer learning using inception_v3. It generates retrained_graph.pb and retrained_labels.txt files, which can used to make predictions locally (running label_image.py)。
然后,我想把这个模型部署到Cloud ML Engine,这样我就可以做在线预测了。为此,我必须将 retrained_graph.pb 导出为 SavedModel 格式。我按照 this answer from Google's @rhaertel80 and this python file from the Flowers Cloud ML Engine Tutorial 中的指示设法做到了。这是我的代码:
import tensorflow as tf
from tensorflow.contrib import layers
from tensorflow.python.saved_model import builder as saved_model_builder
from tensorflow.python.saved_model import signature_constants
from tensorflow.python.saved_model import signature_def_utils
from tensorflow.python.saved_model import tag_constants
from tensorflow.python.saved_model import utils as saved_model_utils
export_dir = '../tf_files/saved7'
retrained_graph = '../tf_files/retrained_graph2.pb'
label_count = 5
def 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_def
class 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 = None
class 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 文件,然后我用它来创建 Cloud ML Engine 模型。我可以使用 gcloud ml-engine predict --model my_model_name --json-instances request.json
从这个模型中得到预测,其中 request.json 的内容是:
{ "key": "0", "image_bytes": { "b64": "jpeg_image_base64_encoded" } }
但是,无论我在请求中编码哪个 jpeg,我总是得到完全相同的错误预测:
Prediction output
我想问题出在 CloudML 预测 API 将 base64 编码图像字节传递给 inception_v3 的输入张量 "DecodeJpeg/contents:0" 的方式("build_image_str_tensor()" 中的方法之前的代码)。关于如何解决此问题并让我的本地再训练模型在 Cloud ML Engine 上提供正确预测的任何线索?
(澄清一下,问题不在 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
将是预测服务所需的一批图像,但初始图的输入实际上是单个图像。为了简单起见,我们将以一种 hacky 的方式解决这个问题:我们假设我们将一张一张地发送图像。如果我们每次请求发送不止一张图片,我们就会出错。此外,批量预测永远不会起作用。
您需要的主要更改是导入语句,它将我们添加的占位符连接到图中的现有输入(您还将看到用于更改输入形状的代码):
把它们放在一起,我们得到类似的东西:
import tensorflow as tf
from tensorflow.contrib import layers
from tensorflow.python.saved_model import builder as saved_model_builder
from tensorflow.python.saved_model import signature_constants
from tensorflow.python.saved_model import signature_def_utils
from tensorflow.python.saved_model import tag_constants
from tensorflow.python.saved_model import utils as saved_model_utils
export_dir = '../tf_files/saved7'
retrained_graph = '../tf_files/retrained_graph2.pb'
label_count = 5
class 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:
# This will be our input that accepts a batch of inputs
image_bytes = tf.placeholder(tf.string, name='input', shape=(None,))
# Force it to be a single input; will raise an error if we send a batch.
coerced = tf.squeeze(image_bytes)
# When we import the graph, we'll connect `coerced` to `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)
我相信你的错误很容易解决:
{ "key": "0", "image_bytes": { "b64": "jpeg_image_base64_encoded" } }
你用 " 来指定什么,我相信,是一个字符串。通过这样做,你的程序正在读取 jpeg_image_base64_encoded 而不是变量的实际值.
这就是为什么您总是得到相同的预测。
对于在 Google Cloud ML 上部署基于 TensorFlow 图像的模型的任何人,特别是试图让 base64 编码适用于图像的人(如本问题中所讨论的),我建议也有一个看看我放在一起的以下回购协议。我花了很多时间完成部署过程,只能在网络上和堆栈溢出中找到部分信息。这个 repo 有一个完整的工作版本,可以将 TensorFlow tf.keras 模型部署到 google 云 ML 上,我认为它会对面临与我相同挑战的人们有所帮助。这是 github link:
https://github.com/mhwilder/tf-keras-gcloud-deployment.
回购涵盖以下主题:
- 在本地训练一个完全卷积的 tf.keras 模型(主要是为了有一个模型来测试接下来的部分)
- 使用 Cloud ML Engine 导出模型的示例代码
- 接受不同 JSON 输入类型的三个模型版本(1. 转换为简单列表字符串的图像,2. 转换为 base64 编码字符串的图像,以及 3. URL指向 Google 存储桶中的图像)
- 一般Google云平台设置的说明和参考
- 为 3 种不同的输入类型准备输入 JSON 文件的代码
- Google 来自控制台的 Cloud ML 模型和版本创建说明
- 使用 Google Cloud SDK 调用模型预测的示例
我关注了代码实验室 TensorFlow For Poets for transfer learning using inception_v3. It generates retrained_graph.pb and retrained_labels.txt files, which can used to make predictions locally (running label_image.py)。
然后,我想把这个模型部署到Cloud ML Engine,这样我就可以做在线预测了。为此,我必须将 retrained_graph.pb 导出为 SavedModel 格式。我按照 this answer from Google's @rhaertel80 and this python file from the Flowers Cloud ML Engine Tutorial 中的指示设法做到了。这是我的代码:
import tensorflow as tf
from tensorflow.contrib import layers
from tensorflow.python.saved_model import builder as saved_model_builder
from tensorflow.python.saved_model import signature_constants
from tensorflow.python.saved_model import signature_def_utils
from tensorflow.python.saved_model import tag_constants
from tensorflow.python.saved_model import utils as saved_model_utils
export_dir = '../tf_files/saved7'
retrained_graph = '../tf_files/retrained_graph2.pb'
label_count = 5
def 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_def
class 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 = None
class 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 文件,然后我用它来创建 Cloud ML Engine 模型。我可以使用 gcloud ml-engine predict --model my_model_name --json-instances request.json
从这个模型中得到预测,其中 request.json 的内容是:
{ "key": "0", "image_bytes": { "b64": "jpeg_image_base64_encoded" } }
但是,无论我在请求中编码哪个 jpeg,我总是得到完全相同的错误预测:
Prediction output
我想问题出在 CloudML 预测 API 将 base64 编码图像字节传递给 inception_v3 的输入张量 "DecodeJpeg/contents:0" 的方式("build_image_str_tensor()" 中的方法之前的代码)。关于如何解决此问题并让我的本地再训练模型在 Cloud ML Engine 上提供正确预测的任何线索?
(澄清一下,问题不在 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
将是预测服务所需的一批图像,但初始图的输入实际上是单个图像。为了简单起见,我们将以一种 hacky 的方式解决这个问题:我们假设我们将一张一张地发送图像。如果我们每次请求发送不止一张图片,我们就会出错。此外,批量预测永远不会起作用。
您需要的主要更改是导入语句,它将我们添加的占位符连接到图中的现有输入(您还将看到用于更改输入形状的代码):
把它们放在一起,我们得到类似的东西:
import tensorflow as tf
from tensorflow.contrib import layers
from tensorflow.python.saved_model import builder as saved_model_builder
from tensorflow.python.saved_model import signature_constants
from tensorflow.python.saved_model import signature_def_utils
from tensorflow.python.saved_model import tag_constants
from tensorflow.python.saved_model import utils as saved_model_utils
export_dir = '../tf_files/saved7'
retrained_graph = '../tf_files/retrained_graph2.pb'
label_count = 5
class 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:
# This will be our input that accepts a batch of inputs
image_bytes = tf.placeholder(tf.string, name='input', shape=(None,))
# Force it to be a single input; will raise an error if we send a batch.
coerced = tf.squeeze(image_bytes)
# When we import the graph, we'll connect `coerced` to `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)
我相信你的错误很容易解决:
{ "key": "0", "image_bytes": { "b64": "jpeg_image_base64_encoded" } }
你用 " 来指定什么,我相信,是一个字符串。通过这样做,你的程序正在读取 jpeg_image_base64_encoded 而不是变量的实际值.
这就是为什么您总是得到相同的预测。
对于在 Google Cloud ML 上部署基于 TensorFlow 图像的模型的任何人,特别是试图让 base64 编码适用于图像的人(如本问题中所讨论的),我建议也有一个看看我放在一起的以下回购协议。我花了很多时间完成部署过程,只能在网络上和堆栈溢出中找到部分信息。这个 repo 有一个完整的工作版本,可以将 TensorFlow tf.keras 模型部署到 google 云 ML 上,我认为它会对面临与我相同挑战的人们有所帮助。这是 github link:
https://github.com/mhwilder/tf-keras-gcloud-deployment.
回购涵盖以下主题:
- 在本地训练一个完全卷积的 tf.keras 模型(主要是为了有一个模型来测试接下来的部分)
- 使用 Cloud ML Engine 导出模型的示例代码
- 接受不同 JSON 输入类型的三个模型版本(1. 转换为简单列表字符串的图像,2. 转换为 base64 编码字符串的图像,以及 3. URL指向 Google 存储桶中的图像)
- 一般Google云平台设置的说明和参考
- 为 3 种不同的输入类型准备输入 JSON 文件的代码
- Google 来自控制台的 Cloud ML 模型和版本创建说明
- 使用 Google Cloud SDK 调用模型预测的示例