Reputation: 863
This is the two methods for creating a keras model, but the output shapes
of the summary results of the two methods are different. Obviously, the former prints more information and makes it easier to check the correctness of the network.
import tensorflow as tf
from tensorflow.keras import Input, layers, Model
class subclass(Model):
def __init__(self):
super(subclass, self).__init__()
self.conv = layers.Conv2D(28, 3, strides=1)
def call(self, x):
return self.conv(x)
def func_api():
x = Input(shape=(24, 24, 3))
y = layers.Conv2D(28, 3, strides=1)(x)
return Model(inputs=[x], outputs=[y])
if __name__ == '__main__':
func = func_api()
func.summary()
sub = subclass()
sub.build(input_shape=(None, 24, 24, 3))
sub.summary()
output:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 24, 24, 3) 0
_________________________________________________________________
conv2d (Conv2D) (None, 22, 22, 28) 784
=================================================================
Total params: 784
Trainable params: 784
Non-trainable params: 0
_________________________________________________________________
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) multiple 784
=================================================================
Total params: 784
Trainable params: 784
Non-trainable params: 0
_________________________________________________________________
So, how should I use the subclass method to get the output shape
at the summary()?
Upvotes: 32
Views: 24283
Reputation: 11
I added only one line(below) in your code.
self.call(Input(shape=(24, 24, 3)))
my code is
import tensorflow as tf
from tensorflow.keras import Input, layers, Model
class subclass(Model):
def __init__(self):
super(subclass, self).__init__()
self.conv = layers.Conv2D(28, 3, strides=1)
# add this code
self.call(Input(shape=(24, 24, 3)))
def call(self, x):
return self.conv(x)
def func_api():
x = Input(shape=(24, 24, 3))
y = layers.Conv2D(28, 3, strides=1)(x)
return Model(inputs=[x], outputs=[y])
if __name__ == '__main__':
func = func_api()
func.summary()
sub = subclass()
sub.build(input_shape=(None, 24, 24, 3))
sub.summary()
result
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 24, 24, 3)] 0
_________________________________________________________________
conv2d (Conv2D) (None, 22, 22, 28) 784
=================================================================
Total params: 784
Trainable params: 784
Non-trainable params: 0
_________________________________________________________________
Model: "subclass"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 22, 22, 28) 784
=================================================================
Total params: 784
Trainable params: 784
Non-trainable params: 0
_______________________________________________________________
Upvotes: 1
Reputation: 51
Gary's answer works. However, for even more convenience, I wanted to access the summary
method of keras.Model
transparently from my custom class objects.
This can be done easily by implementing the builtin __getattr__
method (more info can be found in official Python doc) as follows:
from tensorflow.keras import Input, layers, Model
class MyModel():
def __init__(self):
self.model = self.get_model()
def get_model(self):
# here we use the usual Keras functional API
x = Input(shape=(24, 24, 3))
y = layers.Conv2D(28, 3, strides=1)(x)
return Model(inputs=[x], outputs=[y])
def __getattr__(self, name):
"""
This method enables to access an attribute/method of self.model.
Thus, any method of keras.Model() can be used transparently from a MyModel object
"""
return getattr(self.model, name)
if __name__ == '__main__':
mymodel = MyModel()
mymodel.summary() # underlyingly calls MyModel.model.summary()
Upvotes: 0
Reputation: 41
I have used this method to solve this problem tested on tensorflow 2.1 and tensorflow 2.4.1. Declare the InputLayer with model.inputs_layer
class Logistic(tf.keras.models.Model):
def __init__(self, hidden_size = 5, output_size=1, dynamic=False, **kwargs):
'''
name: String name of the model.
dynamic: (Subclassed models only) Set this to `True` if your model should
only be run eagerly, and should not be used to generate a static
computation graph. This attribute is automatically set for Functional API
models.
trainable: Boolean, whether the model's variables should be trainable.
dtype: (Subclassed models only) Default dtype of the model's weights (
default of `None` means use the type of the first input). This attribute
has no effect on Functional API models, which do not have weights of their
own.
'''
super().__init__(dynamic=dynamic, **kwargs)
self.inputs_ = tf.keras.Input(shape=(2,), name="hello")
self._set_input_layer(self.inputs_)
self.hidden_size = hidden_size
self.dense = layers.Dense(hidden_size, name = "linear")
self.outlayer = layers.Dense(output_size,
activation = 'sigmoid', name = "out_layer")
self.build()
def _set_input_layer(self, inputs):
"""add inputLayer to model and display InputLayers in model.summary()
Args:
inputs ([dict]): the result from `tf.keras.Input`
"""
if isinstance(inputs, dict):
self.inputs_layer = {n: tf.keras.layers.InputLayer(input_tensor=i, name=n)
for n, i in inputs.items()}
elif isinstance(inputs, (list, tuple)):
self.inputs_layer = [tf.keras.layers.InputLayer(input_tensor=i, name=i.name)
for i in inputs]
elif tf.is_tensor(inputs):
self.inputs_layer = tf.keras.layers.InputLayer(input_tensor=inputs, name=inputs.name)
def build(self):
super(Logistic, self).build(self.inputs_.shape if tf.is_tensor(self.inputs_) else self.inputs_)
_ = self.call(self.inputs_)
def call(self, X):
X = self.dense(X)
Y = self.outlayer(X)
return Y
model = Logistic()
model.summary()
Model: "logistic"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
hello:0 (InputLayer) [(None, 2)] 0
_________________________________________________________________
linear (Dense) (None, 5) 15
_________________________________________________________________
out_layer (Dense) (None, 1) 6
=================================================================
Total params: 21
Trainable params: 21
Non-trainable params: 0
_________________________________________________________________
Upvotes: 1
Reputation: 71
I analyzed the answer of Adi Shumely:
So I bring it up and come up with this solution that does not need any modification in the model and just needs to improve the model as it is built before the call to the summary() method by adding a call to the call() method of the model with an Input tensor. I tried on my own model and on the three models presented in this feed and it works so far.
From the first post of this feed:
import tensorflow as tf
from tensorflow.keras import Input, layers, Model
class subclass(Model):
def __init__(self):
super(subclass, self).__init__()
self.conv = layers.Conv2D(28, 3, strides=1)
def call(self, x):
return self.conv(x)
if __name__ == '__main__':
sub = subclass()
sub.build(input_shape=(None, 24, 24, 3))
# Adding this call to the call() method solves it all
sub.call(Input(shape=(24, 24, 3)))
# And the summary() outputs all the information
sub.summary()
From the second post of the feed
from tensorflow import keras
from tensorflow.keras import layers as klayers
class MLP(keras.Model):
def __init__(self, **kwargs):
super(MLP, self).__init__(**kwargs)
self.dense_1 = klayers.Dense(64, activation='relu')
self.dense_2 = klayers.Dense(10)
def call(self, inputs):
x = self.dense_1(inputs)
return self.dense_2(x)
if __name__ == '__main__':
mlp = MLP()
mlp.build(input_shape=(None, 16))
mlp.call(klayers.Input(shape=(16)))
mlp.summary()
As from the last post of the feed
import tensorflow as tf
class MyModel(tf.keras.Model):
def __init__(self, **kwargs):
super(MyModel, self).__init__(**kwargs)
self.dense10 = tf.keras.layers.Dense(10, activation=tf.keras.activations.softmax)
self.dense20 = tf.keras.layers.Dense(20, activation=tf.keras.activations.softmax)
def call(self, inputs):
x = self.dense10(inputs)
y_pred = self.dense20(x)
return y_pred
model = MyModel()
model.build(input_shape = (None, 32, 32, 1))
model.call(tf.keras.layers.Input(shape = (32, 32, 1)))
model.summary()
Upvotes: 7
Reputation: 397
had the same problem - fix it by 3 steps:
class MyModel(tf.keras.Model):
def __init__(self,input_shape=(32,32,1), **kwargs):
super(MyModel, self).__init__(**kwargs)
self.input_layer = tf.keras.layers.Input(input_shape)
self.dense10 = tf.keras.layers.Dense(10, activation=tf.keras.activations.softmax)
self.dense20 = tf.keras.layers.Dense(20, activation=tf.keras.activations.softmax)
self.out = self.call(self.input_layer)
def call(self, inputs):
x = self.dense10(inputs)
y_pred = self.dense20(x)
return y_pred
model = MyModel()
model(x_test[:99])
print('x_test[:99].shape:',x_test[:10].shape)
model.summary()
output:
x_test[:99].shape: (99, 32, 32, 1)
Model: "my_model_32"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_79 (Dense) (None, 32, 32, 10) 20
_________________________________________________________________
dense_80 (Dense) (None, 32, 32, 20) 220
=================================================================
Total params: 240
Trainable params: 240
Non-trainable params: 0
Upvotes: 2
Reputation: 559
The way I solve the problem is very similar to what Elazar mensioned. Override the function summary() in the class subclass
. Then you can directly call summary() while using model subclassing:
class subclass(Model):
def __init__(self):
...
def call(self, x):
...
def summary(self):
x = Input(shape=(24, 24, 3))
model = Model(inputs=[x], outputs=self.call(x))
return model.summary()
if __name__ == '__main__':
sub = subclass()
sub.summary()
Upvotes: 13
Reputation: 79
I guess that key point is the _init_graph_network
method in the class Network
, which is the parent class of Model
. _init_graph_network
will be called if you specify the inputs
and outputs
arguments when calling __init__
method.
So there will be two possible methods:
_init_graph_network
method to build the graph of the model. and both methods need the input layer and output (required from self.call
).
Now calling summary
will give the exact output shape. However it would show the Input
layer, which isn't a part of subclassing Model.
from tensorflow import keras
from tensorflow.keras import layers as klayers
class MLP(keras.Model):
def __init__(self, input_shape=(32), **kwargs):
super(MLP, self).__init__(**kwargs)
# Add input layer
self.input_layer = klayers.Input(input_shape)
self.dense_1 = klayers.Dense(64, activation='relu')
self.dense_2 = klayers.Dense(10)
# Get output layer with `call` method
self.out = self.call(self.input_layer)
# Reinitial
super(MLP, self).__init__(
inputs=self.input_layer,
outputs=self.out,
**kwargs)
def build(self):
# Initialize the graph
self._is_graph_network = True
self._init_graph_network(
inputs=self.input_layer,
outputs=self.out
)
def call(self, inputs):
x = self.dense_1(inputs)
return self.dense_2(x)
if __name__ == '__main__':
mlp = MLP(16)
mlp.summary()
The output will be:
Model: "mlp_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 16)] 0
_________________________________________________________________
dense (Dense) (None, 64) 1088
_________________________________________________________________
dense_1 (Dense) (None, 10) 650
=================================================================
Total params: 1,738
Trainable params: 1,738
Non-trainable params: 0
_________________________________________________________________
Upvotes: 7
Reputation: 863
I have used this method to solve this problem, I don't know if there is an easier way.
class subclass(Model):
def __init__(self):
...
def call(self, x):
...
def model(self):
x = Input(shape=(24, 24, 3))
return Model(inputs=[x], outputs=self.call(x))
if __name__ == '__main__':
sub = subclass()
sub.model().summary()
Upvotes: 33