5

I have a trained keras model that I would like to save to a protocol buffer (.pb) file. When I do so and load the model the predictions are wrong (and different from the original model) and the weights are wrong. Here is the model type:

type(model)
> keras.engine.training.Model

Here is the code I used to freeze and save it to a .pb file.

from keras import backend as K
K.set_learning_phase(0)
import tensorflow as tf
from tensorflow.python.framework.graph_util import convert_variables_to_constants
keras_session = K.get_session()
graph = keras_session.graph
graph.as_default()
keep_var_names=None
output_names=[out.op.name for out in model.outputs]
clear_devices=True
with graph.as_default():
    freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
    output_names = output_names or []
    output_names += [v.op.name for v in tf.global_variables()]
    input_graph_def = graph.as_graph_def()
    if clear_devices:
        for node in input_graph_def.node:
            node.device = ""
    frozen_graph = convert_variables_to_constants(keras_session, input_graph_def,
                                                  output_names, freeze_var_names)
tf.train.write_graph(frozen_graph, "model", "my_model.pb", as_text=False)

Then I read it like so:

pb_file = 'my_model.pb'
with tf.gfile.GFile(pb_file, "rb") as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
with tf.Graph().as_default() as graph:
    tf.import_graph_def(graph_def)
ops = graph.get_operations()
def get_outputs(feed_dict, output_tensor):
    with tf.Session() as sess:

        sess.graph.as_default()
        tf.import_graph_def(graph_def, name='')

        output_tensor_loc = sess.graph.get_tensor_by_name(output_tensor)
        out = sess.run(output_tensor_loc, feed_dict=feed_dict)

        print("Shape is ", out.shape)

        return out

Then, when I compare the weights at the first convolutional layer, they have the same shape (and the shape looks correct) but the weights are different. All the weights are approximately 0:3 while in the original model at the same layer they are approximately -256:256.

get_outputs(feed_dict, 'conv1_relu/Relu:0')

Is there something wrong in the above code? Or is this whole approach wrong? I saw in a blog post someone using tf.train.Saver, which I'm not doing. Do I need to do that? If so, how can I do that to my keras.engine.training.Model?

jss367
  • 4,759
  • 14
  • 54
  • 76
  • There are some git repos already doing this, i have tried a few of them but ended up using an entirely different approach with tensorRT - i can send you a few links if you want to try them yourself (but i need to get home to my pc first) – ItsMeTheBee Aug 15 '19 at 11:29
  • That would be great. Thanks! – jss367 Aug 15 '19 at 15:54

1 Answers1

4

Q: Is there something wrong in the above code? Or is this whole approach wrong?

A: The main problem is that tf.train.write_graph saves the TensorFlow graph, but not the weights of your model.


Q: Do I need to do use tf.train.Saver? If so, how can I do that to my model?

A: Yes. In addition to saving the graph (which is only necessary if your subsequent scripts do not explicitly recreate it), you should use tf.train.Saver to save the weights of your model:

from keras import backend as K

# ... define your model in Keras and do some work

# Add ops to save and restore all the variables.
saver = tf.train.Saver()  # setting var_list=None saves all variables

# Get TensorFlow session
sess = K.get_session()

# save the model's variables
save_path = saver.save(sess, "/tmp/model.ckpt")

Calling saver.save also saves a MetaGraphDef which can then be used to restore the graph, so it is not necessary for you to use tf.train.write_graph. To restore the weights, simply use saver.restore:

with tf.Session() as sess:
  # restore variables from disk
  saver.restore(sess, "/tmp/model.ckpt")

The fact that you are using a Keras model does not change this approach as long as you use the TensorFlow backend (you still have a TensorFlow graph and weights). For more information about saving and restoring models in TensorFlow, please see the save and restore tutorial.


Alternative (neater) way to save a Keras model

Now, since you are using a Keras model, it is perhaps more convenient to save the model with model.save('model_path.h5') and restore it as follows:

from keras.models import load_model

# restore previously saved model
model = load_model('model_path.h5')

UPDATE: Generating a single .pb file from the .ckpt files

If you want to generate a single .pb file, please use the former tf.train.Saver approach. Once you have generated the .ckpt files (.meta holds the graph and .data the weights), you can get the .pb file by calling Morgan's function freeze_graph as follows:

freeze_graph('/tmp', '<Comma separated output node names>') 

References:

  1. Save and restore in TensorFlow.
  2. StackOverflow answer to TensorFlow saving into/loading a graph from a file.
  3. Saving/loading whole models in Keras.
  4. Morgan's function to generate a .pb file from the .ckpt files.
rvinas
  • 11,824
  • 36
  • 58
  • I followed this and got three files: `/tmp/model.ckpt.meta`, `/tmp/model.ckpt.index`, and `/tmp/model.ckpt.data-00000-of-00001`. Now how do I convert those to a `.pb` file? I tried this [SO answer](https://stackoverflow.com/questions/45726365/how-to-convert-ckpt-file-to-pb) and it didn't work. I have used Keras's `model.save` before, but in this case I'm specifically trying to get a `.pb` file, not just save the Keras model. – jss367 Aug 14 '19 at 14:59
  • Alright, then the Keras approach is not appropriate. Please see my updated answer explaining how to get a `.pb` file from the `.ckpt` files. – rvinas Aug 15 '19 at 11:01
  • I followed Morgan's function and got a .pb file. It's exactly the same as the one I get from my original method. They're all 146 MB, so they seem to have most/all of what's needed. I still can't figure out why the weights are different than the original model and why the inference doesn't work. – jss367 Aug 15 '19 at 15:53
  • That's weird. I wonder if there is something wrong in the way you load the graph. In your `get_outputs` function, could you try `with tf.Session(graph=graph) as sess` instead? After that, the next two lines, `sess.graph.as_default()` and `tf.import_graph_def(graph_def, name='')`, should be removed. Also, are you running this in a Jupyter notebook? (if yes, it would be worthwhile checking that you're using the right graph) – rvinas Aug 15 '19 at 16:15
  • I am running this in a Jupyter Notebook. I did as you said and got an error: `KeyError: "The name 'conv1_relu/Relu:0' refers to a Tensor which does not exist. The operation, 'conv1_relu/Relu', does not exist in the graph."` Then I put back the two lines you had me remove but kept in the `with tf.Session(graph=graph) as sess` part. The results are the same as before. – jss367 Aug 15 '19 at 16:42
  • Without those two lines, what are the names of the nodes in your graph? (you can check it with `[n.name for n in graph.as_graph_def().node]`). Do they have a prefix `import`? If they do, you might want to set `name=''` when you initially restore the graph from the `.pb` file (with `tf.import_graph_def`). I am also assuming that you're loading the new graph (generated with `freeze_graph`) – rvinas Aug 15 '19 at 17:09
  • If I do ```with tf.Session() as sess: [n.name for n in graph.as_graph_def().node]``` then `n.name` starts with `import`. So I went to the top and used ```with tf.Graph().as_default() as graph: tf.import_graph_def(graph_def, name='')```. This removed all the `import` strings from the ops (they all used to start with `import`). However, none of the numbers or results appear any different. – jss367 Aug 15 '19 at 17:29
  • I am running out of ideas... It would be worthwhile making sure that you load the right graph and that there isn't any problem with the notebook (i.e. try restarting the kernel). Otherwise, a minimal reproducible example would be really helpful to try the code myself. – rvinas Aug 15 '19 at 18:51
  • I think the issue is although I have a model, I'm not actually loading the weights into anything when I freeze the graph. – jss367 Aug 15 '19 at 21:15
  • As I see it, `freeze_graph` should be saving the weights and the new way of loading the weights seems correct to me (that's how Morgan does it too, in `load_weights`). There might be something else going on – rvinas Aug 17 '19 at 09:14