In this article, I would like to introduce what to do when you try to model.save (or model.to_json) in Keras and get `XX has arguments in` __init __` and therefore must override `get_config
``. ..
In Keras, there are many predefined layers such as Dense layer and Conv layer, and these are combined to design a basic model. But more advanced, you'll have to implement your own custom layers and add them to your model. For example, if you want to take advantage of the mechanism published in the latest paper, it doesn't exist in Keras's predefined layer and you have to quote it from Github or implement it yourself. (If you're curious about implementing custom layers, check out the official examples here (https://github.com/keras-team/keras/blob/master/examples/antirectifier.py).) Alternatively, beginners may unknowingly use a model that includes custom layers when forking a script published in kaggle's kernel, etc. (I myself faced this error that way.)
Now, the error `XX (custom layer name) has arguments in` __init__` and therefore must override `get_config
`` has not been properly addressed for this ** model containing the custom layer ** At that time, Keras angered me, "I don't know such a layer."
This can be solved by overriding `get_config ()`
in the custom layer class.
More specifically, ** make the argument of `__init__``` of the custom layer class into a dictionary, add it to the config of the parent class and return **, etc.
get_config () ` Define. What this means is that the argument of
`init``` is like the design document of this custom layer, so I made it arbitrarily ** I explicitly teach Keras how the custom layer works * * Equivalent to.
By the way, models saved in this way also need to explicitly indicate their custom layers in the custom_objects argument when loading. The method is very simple, do the following:
load_model('my_model.h5', custom_objects={'NameOfCustomLayer': NameOfCustomLayer})
Let's take Kaggle's Public Kernel as an example. [GLRec] ResNet50 ArcFace (TF2.2)
In this script, the actual model definition is done below. The backbone model is predefined in Keras in ResNet50. (The weight can be obtained not only by using the locally saved one like this time, but also by the Keras package.) The pooling and dropout layers are also predefined.
You can see that only the margin layer is instantiated independently. This is the custom layer for this model.
create_model.py
def create_model(input_shape,
n_classes,
dense_units=512,
dropout_rate=0.0,
scale=30,
margin=0.3):
backbone = tf.keras.applications.ResNet50(
include_top=False,
input_shape=input_shape,
weights=('../input/imagenet-weights/' +
'resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5')
)
pooling = tf.keras.layers.GlobalAveragePooling2D(name='head/pooling')
dropout = tf.keras.layers.Dropout(dropout_rate, name='head/dropout')
dense = tf.keras.layers.Dense(dense_units, name='head/dense')
margin = ArcMarginProduct(
n_classes=n_classes,
s=scale,
m=margin,
name='head/arc_margin',
dtype='float32')
softmax = tf.keras.layers.Softmax(dtype='float32')
image = tf.keras.layers.Input(input_shape, name='input/image')
label = tf.keras.layers.Input((), name='input/label')
x = backbone(image)
x = pooling(x)
x = dropout(x)
x = dense(x)
x = margin([x, label])
x = softmax(x)
return tf.keras.Model(
inputs=[image, label], outputs=x)
Check the class of the margin layer, ArcMarginProduct. Then you can see that it is a custom layer that inherits tf.keras.layers.Layer. (By the way, the implemented technology is called ArcFace.)
In this custom layer defined by myself, when I did not properly override `get_config ()`
, I faced the error at the beginning when I did model.save.
In this Kernel, ``` get_config ()` `` is not defined in the class, so if you try to save as it is, an error will occur.
custom_layer.py
class ArcMarginProduct(tf.keras.layers.Layer):
'''
Implements large margin arc distance.
Reference:
https://arxiv.org/pdf/1801.07698.pdf
https://github.com/lyakaap/Landmark2019-1st-and-3rd-Place-Solution/
blob/master/src/modeling/metric_learning.py
'''
def __init__(self, n_classes, s=30, m=0.50, easy_margin=False,
ls_eps=0.0, **kwargs):
super(ArcMarginProduct, self).__init__(**kwargs)
self.n_classes = n_classes
self.s = s
self.m = m
self.ls_eps = ls_eps
self.easy_margin = easy_margin
self.cos_m = tf.math.cos(m)
self.sin_m = tf.math.sin(m)
self.th = tf.math.cos(math.pi - m)
self.mm = tf.math.sin(math.pi - m) * m
def build(self, input_shape):
super(ArcMarginProduct, self).build(input_shape[0])
self.W = self.add_weight(
name='W',
shape=(int(input_shape[0][-1]), self.n_classes),
initializer='glorot_uniform',
dtype='float32',
trainable=True,
regularizer=None)
def call(self, inputs):
X, y = inputs
y = tf.cast(y, dtype=tf.int32)
cosine = tf.matmul(
tf.math.l2_normalize(X, axis=1),
tf.math.l2_normalize(self.W, axis=0)
)
sine = tf.math.sqrt(1.0 - tf.math.pow(cosine, 2))
phi = cosine * self.cos_m - sine * self.sin_m
if self.easy_margin:
phi = tf.where(cosine > 0, phi, cosine)
else:
phi = tf.where(cosine > self.th, phi, cosine - self.mm)
one_hot = tf.cast(
tf.one_hot(y, depth=self.n_classes),
dtype=cosine.dtype
)
if self.ls_eps > 0:
one_hot = (1 - self.ls_eps) * one_hot + self.ls_eps / self.n_classes
output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
output *= self.s
return output
Therefore, you need to make the following changes.
Specifically, it overrides `get_config ()`
and returns the arguments `__init__`
and the config of the parent class.
new_custom_layer.py
class ArcMarginProduct(tf.keras.layers.Layer):
'''
Implements large margin arc distance.
Reference:
https://arxiv.org/pdf/1801.07698.pdf
https://github.com/lyakaap/Landmark2019-1st-and-3rd-Place-Solution/
blob/master/src/modeling/metric_learning.py
'''
def __init__(self, n_classes, s=30, m=0.50, easy_margin=False,
ls_eps=0.0, **kwargs):
super(ArcMarginProduct, self).__init__(**kwargs)
self.n_classes = n_classes
self.s = s
self.m = m
self.ls_eps = ls_eps
self.easy_margin = easy_margin
self.cos_m = tf.math.cos(m)
self.sin_m = tf.math.sin(m)
self.th = tf.math.cos(math.pi - m)
self.mm = tf.math.sin(math.pi - m) * m
###Start added code
def get_config(self):
config = {
"n_classes" : self.n_classes,
"s" : self.s,
"m" : self.m,
"easy_margin" : self.easy_margin,
"ls_eps" : self.ls_eps
}
base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
### End
def build(self, input_shape):
super(ArcMarginProduct, self).build(input_shape[0])
self.W = self.add_weight(
name='W',
shape=(int(input_shape[0][-1]), self.n_classes),
initializer='glorot_uniform',
dtype='float32',
trainable=True,
regularizer=None)
def call(self, inputs):
X, y = inputs
y = tf.cast(y, dtype=tf.int32)
cosine = tf.matmul(
tf.math.l2_normalize(X, axis=1),
tf.math.l2_normalize(self.W, axis=0)
)
sine = tf.math.sqrt(1.0 - tf.math.pow(cosine, 2))
phi = cosine * self.cos_m - sine * self.sin_m
if self.easy_margin:
phi = tf.where(cosine > 0, phi, cosine)
else:
phi = tf.where(cosine > self.th, phi, cosine - self.mm)
one_hot = tf.cast(
tf.one_hot(y, depth=self.n_classes),
dtype=cosine.dtype
)
if self.ls_eps > 0:
one_hot = (1 - self.ls_eps) * one_hot + self.ls_eps / self.n_classes
output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
output *= self.s
return output
And the model should be loaded as follows.
load_model.py
loaded_model =keras.models.load_model("path_to_model", custom_objects = {"ArcMarginProduct": ArcMarginProduct})
How to create a custom layer in Keras
Serialize custom layers with keras
NotImplementedError: Layers with arguments in __init__
must override get_config
Recommended Posts