Closed glenvorel closed 4 years ago
I consider this issue to be a bug, I will work on an update to fix this.
Thanks for pointing this out!
Seconded. Exact same issue when converting body-pix, resulting saved_model does not have any SignatureDefs.
Workaround before the fix is available.
Convert the model generated by TensorFlow.js Graph Model Converter to a graph that can be inspected in TensorBoard.
import_pb_to_tensorboard.py
from the master
branch.
python import_pb_to_tensorboard.py --model_dir /posenet/saved_model --log_dir /posenet/log_dir
import_pb_to_tensorboard.py
from the r2.1
branch - it was the last time the tool supported frozen models.
python import_pb_to_tensorboard.py --model_dir /posenet/frozen_model.pb --log_dir /posenet/log_dir
Load the graph in TensorBoard.
tensorboard --logdir=/posenet/log_dir
Open TensorBoard (localhost:6006
) and identify the input and output nodes. In the case of PoseNet, there is 1 input (sub_2
) are 2 outputs (float_heatmaps
and float_short_offsets
).
Save the SavedModel with SignatureDefs. (Note: This works in TF1.x; when using TF2.x, some imports must be done from tf.compat.v1
)
def get_graph_def_from_saved_model(saved_model_dir):
with tf.Session() as session:
meta_graph_def = tf.saved_model.loader.load(
session,
tags=['serve'],
export_dir=saved_model_dir
)
return meta_graph_def.graph_def
graph_def = get_graph_def_from_saved_model('/posenet/saved_model')
input_nodes = ['sub_2']
output_nodes = ['float_heatmaps', 'float_short_offsets']
with tf.Session(graph=tf.Graph()) as session:
tf.import_graph_def(graph_def, name='')
inputs = {input_node: session.graph.get_tensor_by_name(f'{input_node}:0') for input_node in input_nodes}
outputs = {output_node: session.graph.get_tensor_by_name(f'{output_node}:0') for output_node in output_nodes}
tf.saved_model.simple_save(
session,
'/posenet/savedmodel_signaturedefs',
inputs=inputs,
outputs=outputs
)
Done! Inspect the new SavedModel, it should now contain SignatureDefs.
saved_model_cli show --dir /posenet/savedmodel_signaturedefs --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['sub_2'] tensor_info:
dtype: DT_FLOAT
shape: (1, -1, -1, 3)
name: sub_2:0
The given SavedModel SignatureDef contains the following output(s):
outputs['float_heatmaps'] tensor_info:
dtype: DT_FLOAT
shape: (1, -1, -1, 17)
name: float_heatmaps:0
outputs['float_short_offsets'] tensor_info:
dtype: DT_FLOAT
shape: (1, -1, -1, 34)
name: float_short_offsets:0
Method name is: tensorflow/serving/predict
@glenvorel thanks for the workaround! Also works with TF2 if you use tf.compat.v1
prefix for everything. JFYI, for steps 1-3, you can also use Netron instead (https://lutzroeder.github.io/netron/).
Additional comment: if you want to continue to convert the result to TFLite (which I did), then the following code snippet from the converter API guide may be helpful (this can basically be appended right below the code example from @glenvorel ):
model = tf.saved_model.load(export_dir)
concrete_func = model.signatures[
tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY]
concrete_func.inputs[0].set_shape([1, 256, 256, 3])
converter = TFLiteConverter.from_concrete_functions([concrete_func])
Thank you for the quick fix! While testing it, I ran into some import issues.
The traceback below is common when I:
tensorflow/tensorflow:2.2.0
, then pip install tfjs-graph-converter==1.1.0
.tensorflow/tensorflow:2.3.0
, then pip install tfjs-graph-converter==1.1.0
.python:3.8
, then pip install tensorflow==2.3.0 tfjs-graph-converter==1.1.0
.Traceback (most recent call last):
File "/usr/local/bin/tfjs_graph_converter", line 5, in <module>
from tfjs_graph_converter.converter import pip_main
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/__init__.py", line 4, in <module>
from tfjs_graph_converter import api # noqa: F401
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/api.py", line 29, in <module>
from tfjs_graph_converter.optimization import optimize_graph
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/optimization.py", line 19, in <module>
from tfjs_graph_converter.util import get_input_nodes, get_output_nodes
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/util.py", line 13, in <module>
from tensorflow_core.core.protobuf.meta_graph_pb2 import SignatureDef
ModuleNotFoundError: No module named 'tensorflow_core'
This comment says that tensorflow_core
only exists in 1.15, 2.0 and 2.1 so I gave it another try:
tensorflow/tensorflow:2.1.0-py3
, then pip install tfjs-graph-converter==1.1.0
.The tensorflow_core
module was imported successfully but there was an issue importing AttrValue
.
Traceback (most recent call last):
File "/usr/local/bin/tfjs_graph_converter", line 5, in <module>
from tfjs_graph_converter.converter import pip_main
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/__init__.py", line 4, in <module>
from tfjs_graph_converter import api # noqa: F401
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/api.py", line 29, in <module>
from tfjs_graph_converter.optimization import optimize_graph
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/optimization.py", line 19, in <module>
from tfjs_graph_converter.util import get_input_nodes, get_output_nodes
File "/usr/local/lib/python3.6/dist-packages/tfjs_graph_converter/util.py", line 13, in <module>
from tensorflow_core.core.protobuf.meta_graph_pb2 import SignatureDef
File "/usr/local/lib/python3.6/dist-packages/tensorflow_core/__init__.py", line 46, in <module>
from . _api.v2 import compat
File "/usr/local/lib/python3.6/dist-packages/tensorflow_core/_api/v2/compat/__init__.py", line 39, in <module>
from . import v1
File "/usr/local/lib/python3.6/dist-packages/tensorflow_core/_api/v2/compat/v1/__init__.py", line 32, in <module>
from . import compat
File "/usr/local/lib/python3.6/dist-packages/tensorflow_core/_api/v2/compat/v1/compat/__init__.py", line 39, in <module>
from . import v1
File "/usr/local/lib/python3.6/dist-packages/tensorflow_core/_api/v2/compat/v1/compat/v1/__init__.py", line 82, in <module>
from tensorflow.python import AttrValue
ImportError: cannot import name 'AttrValue'
AttrValue
import error? Below is pip freeze
of tensorflow/tensorflow:2.1.0-py3
and pip install tfjs-graph-converter==1.1.0
:absl-py==0.9.0
asn1crypto==0.24.0
astor==0.8.1
astunparse==1.6.3
cachetools==4.0.0
certifi==2019.11.28
chardet==3.0.4
cryptography==2.1.4
gast==0.3.3
google-auth==1.10.0
google-auth-oauthlib==0.4.1
google-pasta==0.1.8
grpcio==1.26.0
h5py==2.10.0
idna==2.6
Keras-Applications==1.0.8
Keras-Preprocessing==1.1.2
keyring==10.6.0
keyrings.alt==3.0
Markdown==3.1.1
numpy==1.18.1
oauthlib==3.1.0
opt-einsum==3.1.0
prompt-toolkit==1.0.14
protobuf==3.11.2
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycrypto==2.6.1
Pygments==2.6.1
pygobject==3.26.1
PyInquirer==1.0.3
pyxdg==0.25
regex==2020.7.14
requests==2.22.0
requests-oauthlib==1.3.0
rsa==4.0
scipy==1.4.1
SecretStorage==2.3.1
six==1.13.0
tensorboard==2.3.0
tensorboard-plugin-wit==1.7.0
tensorflow==2.1.0
tensorflow-cpu==2.3.0
tensorflow-estimator==2.3.0
tensorflow-hub==0.7.0
tensorflowjs==2.0.1.post1
termcolor==1.1.0
tfjs-graph-converter==1.1.0
urllib3==1.25.7
wcwidth==0.2.5
Werkzeug==0.16.0
wrapt==1.11.2
@glenvorel Thank you for making me aware of the compatibility issue! Your tensorflow install seems to be broken, though. The versions of tensorflow-cpu and tensorflow don't seem to match. You basically have a mixture of TF 2.1 and TF 2.3 installed and that could be the issue here.
I will do some version-compatibility testing with TF 2.2 and TF 2.3 and release an update late today.
In the meantime make sure that all tensorflow-* packages in your environment have matching version numbers to avoid problems. The AttrValue
-issue stems from within tensorflow itself, likely due to the aforementioned version conflict.
@glenvorel The package now works as-is with tensorflow v2.2 and v2.3 as well.
You can just use an empty (conda-)environment and use pip install tfjs_graph_converter
, which will pull in all dependencies in the latest versions (including TF v2.3) and works as-is.
This is really great, I really appreciate the quick fix! As you said, it now pulls TF2.3 and performs the conversion.
However, while testing it, I noticed that the tensor shape is different from my workaround method.
SavedModel via workaround:
$ saved_model_cli show --dir /posenet/saved_model_workaround --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['sub_2'] tensor_info:
dtype: DT_FLOAT
shape: (1, -1, -1, 3)
name: sub_2:0
The given SavedModel SignatureDef contains the following output(s):
outputs['float_heatmaps'] tensor_info:
dtype: DT_FLOAT
shape: (1, -1, -1, 17)
name: float_heatmaps:0
outputs['float_short_offsets'] tensor_info:
dtype: DT_FLOAT
shape: (1, -1, -1, 34)
name: float_short_offsets:0
Method name is: tensorflow/serving/predict
SavedModel via tfjs-graph-converter:
$ saved_model_cli show --dir /posenet/saved_model_converter --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['sub_2:0'] tensor_info:
dtype: DT_FLOAT
shape: (1, -1, -1, 3)
name: sub_2:0
The given SavedModel SignatureDef contains the following output(s):
outputs['float_heatmaps:0'] tensor_info:
dtype: DT_FLOAT
shape: (17)
name: Const_114:0
outputs['float_short_offsets:0'] tensor_info:
dtype: DT_FLOAT
shape: (34)
name: Const_112:0
outputs['resnet_v1_50/displacement_bwd_2/BiasAdd:0'] tensor_info:
dtype: DT_FLOAT
shape: (32)
name: Const_110:0
outputs['resnet_v1_50/displacement_fwd_2/BiasAdd:0'] tensor_info:
dtype: DT_FLOAT
shape: (32)
name: Const:0
Method name is: tensorflow/serving/predict
For example, when I perform inference on a sample image which has 1280x720 pixels using PoseNet with stride 16, the response from model produced by tfjs-graph-converter is quite different (significant part of information is missing so it is not possible to determine positions of the keypoints).
Model | tf_result_proto.ByteSize() | float_heatmaps.shape | float_short_offsets.shape |
---|---|---|---|
saved_model_workaround | 734533 | (1, 45, 80, 17) | (1, 45, 80, 34) |
saved_model_converter | 704 | (17) | (34) |
I don't know if this only affects PoseNet models or is a more broad issue. Just wanted to bring it to your attention.
Additional comment: if you want to continue to convert the result to TFLite (which I did), then the following code snippet from the converter API guide may be helpful (this can basically be appended right below the code example from @glenvorel ):
model = tf.saved_model.load(export_dir) concrete_func = model.signatures[ tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY] concrete_func.inputs[0].set_shape([1, 256, 256, 3]) converter = TFLiteConverter.from_concrete_functions([concrete_func])
Thanks, @floe, Using this code I can able to generate TFLite model. But how to decode heatmap and offset in order to generate detected keypoints.
Here is the output details of the TFLite model:
interpreter.get_output_details()
output_details
[{'name': 'float_heatmaps',
'index': 195,
'shape': array([ 1, 16, 16, 17], dtype=int32),
'shape_signature': array([ 1, 16, 16, 17], dtype=int32),
'dtype': numpy.float32,
'quantization': (0.0, 0),
'quantization_parameters': {'scales': array([], dtype=float32),
'zero_points': array([], dtype=int32),
'quantized_dimension': 0},
'sparsity_parameters': {}},
{'name': 'float_short_offsets',
'index': 196,
'shape': array([ 1, 16, 16, 34], dtype=int32),
'shape_signature': array([ 1, 16, 16, 34], dtype=int32),
'dtype': numpy.float32,
'quantization': (0.0, 0),
'quantization_parameters': {'scales': array([], dtype=float32),
'zero_points': array([], dtype=int32),
'quantized_dimension': 0},
'sparsity_parameters': {}},
{'name': 'resnet_v1_50/displacement_bwd_2/BiasAdd',
'index': 197,
'shape': array([ 1, 16, 16, 32], dtype=int32),
'shape_signature': array([ 1, 16, 16, 32], dtype=int32),
'dtype': numpy.float32,
'quantization': (0.0, 0),
'quantization_parameters': {'scales': array([], dtype=float32),
'zero_points': array([], dtype=int32),
'quantized_dimension': 0},
'sparsity_parameters': {}},
{'name': 'resnet_v1_50/displacement_fwd_2/BiasAdd',
'index': 198,
'shape': array([ 1, 16, 16, 32], dtype=int32),
'shape_signature': array([ 1, 16, 16, 32], dtype=int32),
'dtype': numpy.float32,
'quantization': (0.0, 0),
'quantization_parameters': {'scales': array([], dtype=float32),
'zero_points': array([], dtype=int32),
'quantized_dimension': 0},
'sparsity_parameters': {}}]
Please help me to get the dimension of the detected key-points along with a score.
Thanks, Bhavika
I've never used the keypoints, just the heatmap - sorry. I guess you'd have to look in the body-pix JS demo code.
I've never used the keypoints, just the heatmap - sorry. I guess you'd have to look in the body-pix JS demo code.
Thanks, @floe for your response. But How you have used heatmap?
I only used the output node float_segments
and classified everything above 0.65 as body part, nothing more.
Conversion of TFJS model (PoseNet ResNet50) to SavedModel is successful but the produced SavedModel doesn't seem to contain any signature keys, inputs and outputs.
It means that TensorFlow Serving cannot serve this model.
My idea was to load the produced model as GraphDef
find name of the input
and then save it again
but I don't know how to find names of the output nodes. Can you please suggest? Or can you think of a better way of generating SignatureDefs for the SavedModel?
Thank you for this useful tool!