Repository: kymatio/kymatio

Issue:
Keras not saving model architecture


**Hi everybody, 
I have included in a Keras model a scattering layer using the following syntax:**

```
import kymatio
from kymatio.keras import Scattering2D

# Model architecture
def Dual_ResNet_Atrous_Att_WST(input_shape=(HEIGHT, WIDTH, CHANNELS),
                   mra_channels=MRACHANNELS,
                   num_class=NUM_CLASS,
                   ks1=KS1, ks2=KS2, ks3=KS3, 
                   dl1=DL1, dl2=DL2, dl3=DL3,
                   filters=NF,resblock1=NR1,
                   r_filters=NFL, resblock2=NR2,
                   dil_mode=DIL_MODE, 
                   sp_dropout=DR1,re_dropout=DR2,
                   mask_float=True,
                   attention=None,
                   mask_threshold=0.5,
                   return_seq=True):

    input_1 = Input(shape=(input_shape[0],input_shape[1],mra_channels))    
    input_2 = Input(shape=input_shape)
    input_3 = tf.where(tf.greater(input_2,mask_threshold),1.0,0.0)
    
    if attention == 'wst':
        # wavelet scattering transform (WST)
        d01 = UpSampling2D(size=(4, 4), interpolation="bilinear")(input_1)
        d02 = Lambda(lambda x: x[:,:,:,0])(d01)
        d03 = Scattering2D(J=2,L=6)(d02)
        d0 = tf.transpose(d03,perm=[0, 2, 3, 1])
    
    for cycle in range(resblock1):
        if cycle == 0:            
            d1 = stem_split_3k(d0, filters, mode=dil_mode, 
                                kernel_size_1=ks1, kernel_size_2=ks2, kernel_size_3=ks3, 
                                dilation_1=dl1, dilation_2=dl2, dilation_3=dl3,
                                kernel_regularizer=None)                      
        else:
            d1 = residual_block_split_3k(d1, filters, mode=dil_mode, 
                                         kernel_size_1=ks1, kernel_size_2=ks2, kernel_size_3=ks3, 
                                         dilation_1=dl1, dilation_2=dl2, dilation_3=dl3,
                                         kernel_regularizer=None) 
            

        d1 = SpatialDropout2D(sp_dropout)(d1)            
                 
    d2 = residual_block_split_3k(d1, num_class, mode="add", 
                                    kernel_size_1=ks1, kernel_size_2=ks2, kernel_size_3=ks3, 
                                    dilation_1=dl1, dilation_2=dl2,dilation_3=dl3,
                                    kernel_regularizer=regularizers.l1_l2(l1=1e-5, l2=1e-4))
        
    d3 = LeakyReLU(alpha=0.1)(d2)
    
    if mask_float:
        d3 = Multiply()([d3, input_2])
    else:
        d3 = Multiply()([d3, input_3])
   
    d3 = GlobalAveragePooling2D()(d3)    
    
    output_1 = Activation("softmax", name = 'softmax')(d3)    
    
    model = Model([input_1,input_2], output_1, name='Dual_Res-CR-Net_Att_WST')    

    return model

```

 **The model compiles without problems:**

```
# Model compilation
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
    model = Dual_ResNet_Atrous_Att_WST(input_shape=(HEIGHT, WIDTH, CHANNELS),
                   mra_channels=MRACHANNELS,                          
                   num_class=NUM_CLASS,
                   ks1=KS1, ks2=KS2, ks3=KS3, 
                   dl1=DL1, dl2=DL2, dl3=DL3,
                   filters=NF,resblock1=NR1,
                   r_filters=NFL, resblock2=NR2,
                   dil_mode=DIL_MODE, 
                   sp_dropout=DR1,re_dropout=DR2,
                   mask_float=True,
                   attention='wst',
                   mask_threshold=0.5,                                         
                   return_seq=True)
        
    model.compile(optimizer=Adam(), 
                      loss=tf.keras.losses.CategoricalCrossentropy(), 
                      metrics=[tf.keras.metrics.CategoricalAccuracy()])

model.summary()
```

Model: Dual_Res-CR-Net_Att_WST

input_1 (InputLayer)            [(None, 300, 340, 1) 0                                            
__________________________________________________________________________________________________
up_sampling2d (UpSampling2D)    (None, 1200, 1360, 1 0           input_1[0][0]                    
__________________________________________________________________________________________________
lambda (Lambda)                 (None, 1200, 1360)   0           up_sampling2d[0][0]              
__________________________________________________________________________________________________
scattering2d (Scattering2D)     (None, 49, 300, 340) 0           lambda[0][0]                     
__________________________________________________________________________________________________
tf_op_layer_transpose (TensorFl [(None, 300, 340, 49 0           scattering2d[0][0]               
__________________________________________________________________________________________________
separable_conv2d (SeparableConv (None, 300, 340, 16) 1241        tf_op_layer_transpose[0][0]      
__________________________________________________________________________________________________
separable_conv2d_2 (SeparableCo (None, 300, 340, 16) 2025        tf_op_layer_transpose[0][0]      
__________________________________________________________________________________________________
separable_conv2d_4 (SeparableCo (None, 300, 340, 16) 3201        tf_op_layer_transpose[0][0]      
__________________________________________________________________________________________________
batch_normalization (BatchNorma (None, 300, 340, 16) 64          separable_conv2d[0][0]           
__________________________________________________________________________________________________
batch_normalization_1 (BatchNor (None, 300, 340, 16) 64          separable_conv2d_2[0][0]         
__________________________________________________________________________________________________
batch_normalization_2 (BatchNor (None, 300, 340, 16) 64          separable_conv2d_4[0][0]         
__________________________________________________________________________________________________
leaky_re_lu (LeakyReLU)         (None, 300, 340, 16) 0           batch_normalization[0][0]        
__________________________________________________________________________________________________
leaky_re_lu_1 (LeakyReLU)       (None, 300, 340, 16) 0           batch_normalization_1[0][0]      
__________________________________________________________________________________________________
leaky_re_lu_2 (LeakyReLU)       (None, 300, 340, 16) 0           batch_normalization_2[0][0]      
__________________________________________________________________________________________________
conv2d (Conv2D)                 (None, 300, 340, 48) 2400        tf_op_layer_transpose[0][0]      
__________________________________________________________________________________________________
separable_conv2d_1 (SeparableCo (None, 300, 340, 16) 416         leaky_re_lu[0][0]                
__________________________________________________________________________________________________
separable_conv2d_3 (SeparableCo (None, 300, 340, 16) 672         leaky_re_lu_1[0][0]              
__________________________________________________________________________________________________
separable_conv2d_5 (SeparableCo (None, 300, 340, 16) 1056        leaky_re_lu_2[0][0]              
__________________________________________________________________________________________________
batch_normalization_3 (BatchNor (None, 300, 340, 48) 192         conv2d[0][0]                     
__________________________________________________________________________________________________
concatenate (Concatenate)       (None, 300, 340, 48) 0           separable_conv2d_1[0][0]         
                                                                 separable_conv2d_3[0][0]         
                                                                 separable_conv2d_5[0][0]         
__________________________________________________________________________________________________
add (Add)                       (None, 300, 340, 48) 0           batch_normalization_3[0][0]      
                                                                 concatenate[0][0]                
__________________________________________________________________________________________________
spatial_dropout2d (SpatialDropo (None, 300, 340, 48) 0           add[0][0]                        
__________________________________________________________________________________________________
batch_normalization_4 (BatchNor (None, 300, 340, 48) 192         spatial_dropout2d[0][0]          
__________________________________________________________________________________________________
leaky_re_lu_3 (LeakyReLU)       (None, 300, 340, 48) 0           batch_normalization_4[0][0]      
__________________________________________________________________________________________________
separable_conv2d_6 (SeparableCo (None, 300, 340, 16) 1216        leaky_re_lu_3[0][0]              
__________________________________________________________________________________________________
separable_conv2d_8 (SeparableCo (None, 300, 340, 16) 1984        leaky_re_lu_3[0][0]              
__________________________________________________________________________________________________
separable_conv2d_10 (SeparableC (None, 300, 340, 16) 3136        leaky_re_lu_3[0][0]              
__________________________________________________________________________________________________
batch_normalization_5 (BatchNor (None, 300, 340, 16) 64          separable_conv2d_6[0][0]         
__________________________________________________________________________________________________
batch_normalization_6 (BatchNor (None, 300, 340, 16) 64          separable_conv2d_8[0][0]         
__________________________________________________________________________________________________
batch_normalization_7 (BatchNor (None, 300, 340, 16) 64          separable_conv2d_10[0][0]        
__________________________________________________________________________________________________
leaky_re_lu_4 (LeakyReLU)       (None, 300, 340, 16) 0           batch_normalization_5[0][0]      
__________________________________________________________________________________________________
leaky_re_lu_5 (LeakyReLU)       (None, 300, 340, 16) 0           batch_normalization_6[0][0]      
__________________________________________________________________________________________________
leaky_re_lu_6 (LeakyReLU)       (None, 300, 340, 16) 0           batch_normalization_7[0][0]      
__________________________________________________________________________________________________
conv2d_1 (Conv2D)               (None, 300, 340, 48) 2352        spatial_dropout2d[0][0]          
__________________________________________________________________________________________________
separable_conv2d_7 (SeparableCo (None, 300, 340, 16) 416         leaky_re_lu_4[0][0]              
__________________________________________________________________________________________________
separable_conv2d_9 (SeparableCo (None, 300, 340, 16) 672         leaky_re_lu_5[0][0]              
__________________________________________________________________________________________________
separable_conv2d_11 (SeparableC (None, 300, 340, 16) 1056        leaky_re_lu_6[0][0]              
__________________________________________________________________________________________________
batch_normalization_8 (BatchNor (None, 300, 340, 48) 192         conv2d_1[0][0]                   
__________________________________________________________________________________________________
concatenate_1 (Concatenate)     (None, 300, 340, 48) 0           separable_conv2d_7[0][0]         
                                                                 separable_conv2d_9[0][0]         
                                                                 separable_conv2d_11[0][0]        
__________________________________________________________________________________________________
add_1 (Add)                     (None, 300, 340, 48) 0           batch_normalization_8[0][0]      
                                                                 concatenate_1[0][0]              
__________________________________________________________________________________________________
spatial_dropout2d_1 (SpatialDro (None, 300, 340, 48) 0           add_1[0][0]                      
__________________________________________________________________________________________________
batch_normalization_9 (BatchNor (None, 300, 340, 48) 192         spatial_dropout2d_1[0][0]        
__________________________________________________________________________________________________
leaky_re_lu_7 (LeakyReLU)       (None, 300, 340, 48) 0           batch_normalization_9[0][0]      
__________________________________________________________________________________________________
separable_conv2d_12 (SeparableC (None, 300, 340, 2)  530         leaky_re_lu_7[0][0]              
__________________________________________________________________________________________________
separable_conv2d_14 (SeparableC (None, 300, 340, 2)  1298        leaky_re_lu_7[0][0]              
__________________________________________________________________________________________________
separable_conv2d_16 (SeparableC (None, 300, 340, 2)  2450        leaky_re_lu_7[0][0]              
__________________________________________________________________________________________________
batch_normalization_10 (BatchNo (None, 300, 340, 2)  8           separable_conv2d_12[0][0]        
__________________________________________________________________________________________________
batch_normalization_11 (BatchNo (None, 300, 340, 2)  8           separable_conv2d_14[0][0]        
__________________________________________________________________________________________________
batch_normalization_12 (BatchNo (None, 300, 340, 2)  8           separable_conv2d_16[0][0]        
__________________________________________________________________________________________________
conv2d_2 (Conv2D)               (None, 300, 340, 2)  98          spatial_dropout2d_1[0][0]        
__________________________________________________________________________________________________
leaky_re_lu_8 (LeakyReLU)       (None, 300, 340, 2)  0           batch_normalization_10[0][0]     
__________________________________________________________________________________________________
leaky_re_lu_9 (LeakyReLU)       (None, 300, 340, 2)  0           batch_normalization_11[0][0]     
__________________________________________________________________________________________________
leaky_re_lu_10 (LeakyReLU)      (None, 300, 340, 2)  0           batch_normalization_12[0][0]     
__________________________________________________________________________________________________
batch_normalization_13 (BatchNo (None, 300, 340, 2)  8           conv2d_2[0][0]                   
__________________________________________________________________________________________________
separable_conv2d_13 (SeparableC (None, 300, 340, 2)  24          leaky_re_lu_8[0][0]              
__________________________________________________________________________________________________
separable_conv2d_15 (SeparableC (None, 300, 340, 2)  56          leaky_re_lu_9[0][0]              
__________________________________________________________________________________________________
separable_conv2d_17 (SeparableC (None, 300, 340, 2)  104         leaky_re_lu_10[0][0]             
__________________________________________________________________________________________________
add_2 (Add)                     (None, 300, 340, 2)  0           batch_normalization_13[0][0]     
                                                                 separable_conv2d_13[0][0]        
                                                                 separable_conv2d_15[0][0]        
                                                                 separable_conv2d_17[0][0]        
__________________________________________________________________________________________________
leaky_re_lu_11 (LeakyReLU)      (None, 300, 340, 2)  0           add_2[0][0]  
__________________________________________________________________________________________________                    
input_2 (InputLayer)            [(None, 300, 340, 1) 0 
__________________________________________________________________________________________________                                           
multiply (Multiply)             (None, 300, 340, 2)  0           leaky_re_lu_11[0][0]             
                                                                 input_2[0][0] 
__________________________________________________________________________________________________                   
global_average_pooling2d (Globa (None, 2)            0           multiply[0][0] 
__________________________________________________________________________________________________                  
softmax (Activation)            (None, 2)            0           global_average_pooling2d[0][0]  
__________________________________________________________________________________________________ 


Total params: 27,587
Trainable params: 26,995
Non-trainable params: 592

**and runs very nicely and very fast (for example, just 3 epochs), however with a warning for not being able to serialize as JSON and a second warning: Layers with arguments in `__init__` must override `get_config`.**

```
# TRAINING
epoch_num = 3
train_steps = len(train_generator)
val_steps = len(valid_generator)

history = model.fit(train_generator, 
                    validation_data=valid_generator,
                    steps_per_epoch=train_steps,
                    validation_steps=val_steps,
                    epochs=epoch_num,
                    initial_epoch=initial_epoch,
                    max_queue_size =15,
                    workers=1,
                    callbacks = callbacks_list)
```
  
Train for 81 steps, validate for 16 steps
WARNING:tensorflow:Model failed to serialize as JSON. Ignoring... Layers with arguments in `__init__` must override `get_config`.
Epoch 1/3
INFO:tensorflow:batch_all_reduce: 88 all-reduces with algorithm = nccl, num_packs = 1, agg_small_grads_max_bytes = 0 and agg_small_grads_max_group = 10
INFO:tensorflow:batch_all_reduce: 88 all-reduces with algorithm = nccl, num_packs = 1, agg_small_grads_max_bytes = 0 and agg_small_grads_max_group = 10
81/81 [==============================] - 535s 7s/step - loss: 0.1260 - categorical_accuracy: 0.3951 - val_loss: 0.1268 - val_categorical_accuracy: 0.3000
Epoch 2/3
81/81 [==============================] - 146s 2s/step - loss: 0.1206 - categorical_accuracy: 0.3160 - val_loss: 0.1237 - val_categorical_accuracy: 0.3000
Epoch 3/3
81/81 [==============================] - 146s 2s/step - loss: 0.1178 - categorical_accuracy: 0.3185 - val_loss: 0.1192 - val_categorical_accuracy: 0.3000

**However if I try to save the model architecture without weights:**
 
`model.save('models/3_epoch_' + model_selection + '_' + model_number + '_arch' + '.h5')`

**I get:**
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-27-abc9f39fa94f> in <module>
      1 # In[12]:
      2 # Save architecture without weights as h5
----> 3 model.save('models/3_epoch_' + model_selection + '_' + model_number + '_arch' + '.h5')

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/network.py in save(self, filepath, overwrite, include_optimizer, save_format, signatures, options)
   1006     """
   1007     save.save_model(self, filepath, overwrite, include_optimizer, save_format,
-> 1008                     signatures, options)
   1009 
   1010   def save_weights(self, filepath, overwrite=True, save_format=None):

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/save.py in save_model(model, filepath, overwrite, include_optimizer, save_format, signatures, options)
    110           'or using `save_weights`.')
    111     hdf5_format.save_model_to_hdf5(
--> 112         model, filepath, overwrite, include_optimizer)
    113   else:
    114     saved_model_save.save(model, filepath, overwrite, include_optimizer,

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/hdf5_format.py in save_model_to_hdf5(model, filepath, overwrite, include_optimizer)
     97 
     98   try:
---> 99     model_metadata = saving_utils.model_metadata(model, include_optimizer)
    100     for k, v in model_metadata.items():
    101       if isinstance(v, (dict, list, tuple)):

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/saving_utils.py in model_metadata(model, include_optimizer, require_config)
    170   except NotImplementedError as e:
    171     if require_config:
--> 172       raise e
    173 
    174   metadata = dict(

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/saving_utils.py in model_metadata(model, include_optimizer, require_config)
    167   model_config = {'class_name': model.__class__.__name__}
    168   try:
--> 169     model_config['config'] = model.get_config()
    170   except NotImplementedError as e:
    171     if require_config:

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/network.py in get_config(self)
    916     if not self._is_graph_network:
    917       raise NotImplementedError
--> 918     return copy.deepcopy(get_network_config(self))
    919 
    920   @classmethod

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/network.py in get_network_config(network, serialize_layer_fn)
   1991           filtered_inbound_nodes.append(node_data)
   1992 
-> 1993     layer_config = serialize_layer_fn(layer)
   1994     layer_config['name'] = layer.name
   1995     layer_config['inbound_nodes'] = filtered_inbound_nodes

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/utils/generic_utils.py in serialize_keras_object(instance)
    196 
    197   if hasattr(instance, 'get_config'):
--> 198     config = instance.get_config()
    199     serialization_config = {}
    200     for key, item in config.items():

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/base_layer.py in get_config(self)
    497     # or that `get_config` has been overridden:
    498     if len(extra_args) > 1 and hasattr(self.get_config, '_is_default'):
--> 499       raise NotImplementedError('Layers with arguments in `__init__` must '
    500                                 'override `get_config`.')
    501     return config

NotImplementedError: Layers with arguments in `__init__` must override `get_config`.

 
**Alternatively I can try to save the model including the weights:**

```
model.save('models/3_epoch_' + model_selection + '_' + model_number + '_all' + '.h5',
        overwrite=True,include_optimizer=False,save_format='h5',signatures=None,options=None)
```

**in which case I also get:**
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-26-ccabc94d774e> in <module>
      3 # Save architecture + weights
      4 model.save('models/3_epoch_' + model_selection + '_' + model_number + '_all' + '.h5',
----> 5         overwrite=True,include_optimizer=False,save_format='h5',signatures=None,options=None)

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/network.py in save(self, filepath, overwrite, include_optimizer, save_format, signatures, options)
   1006     """
   1007     save.save_model(self, filepath, overwrite, include_optimizer, save_format,
-> 1008                     signatures, options)
   1009 
   1010   def save_weights(self, filepath, overwrite=True, save_format=None):

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/save.py in save_model(model, filepath, overwrite, include_optimizer, save_format, signatures, options)
    110           'or using `save_weights`.')
    111     hdf5_format.save_model_to_hdf5(
--> 112         model, filepath, overwrite, include_optimizer)
    113   else:
    114     saved_model_save.save(model, filepath, overwrite, include_optimizer,

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/hdf5_format.py in save_model_to_hdf5(model, filepath, overwrite, include_optimizer)
     97 
     98   try:
---> 99     model_metadata = saving_utils.model_metadata(model, include_optimizer)
    100     for k, v in model_metadata.items():
    101       if isinstance(v, (dict, list, tuple)):

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/saving_utils.py in model_metadata(model, include_optimizer, require_config)
    170   except NotImplementedError as e:
    171     if require_config:
--> 172       raise e
    173 
    174   metadata = dict(

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/saving_utils.py in model_metadata(model, include_optimizer, require_config)
    167   model_config = {'class_name': model.__class__.__name__}
    168   try:
--> 169     model_config['config'] = model.get_config()
    170   except NotImplementedError as e:
    171     if require_config:

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/network.py in get_config(self)
    916     if not self._is_graph_network:
    917       raise NotImplementedError
--> 918     return copy.deepcopy(get_network_config(self))
    919 
    920   @classmethod

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/network.py in get_network_config(network, serialize_layer_fn)
   1991           filtered_inbound_nodes.append(node_data)
   1992 
-> 1993     layer_config = serialize_layer_fn(layer)
   1994     layer_config['name'] = layer.name
   1995     layer_config['inbound_nodes'] = filtered_inbound_nodes

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/utils/generic_utils.py in serialize_keras_object(instance)
    196 
    197   if hasattr(instance, 'get_config'):
--> 198     config = instance.get_config()
    199     serialization_config = {}
    200     for key, item in config.items():

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/engine/base_layer.py in get_config(self)
    497     # or that `get_config` has been overridden:
    498     if len(extra_args) > 1 and hasattr(self.get_config, '_is_default'):
--> 499       raise NotImplementedError('Layers with arguments in `__init__` must '
    500                                 'override `get_config`.')
    501     return config

NotImplementedError: Layers with arguments in `__init__` must override `get_config`.

**In both cases a .h5 file is made, but if I try to reload the model from either saved .h5, the loaded file does not contain a model.:**

```
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():

    filepath = 'models/' + model_selection + '_' + model_number + '.h5'    
    model = load_model(filepath, compile=False)     

    model.compile(optimizer=Adam(), 
                  loss=tf.keras.losses.CategoricalCrossentropy(), 
                  metrics=[tf.keras.metrics.CategoricalAccuracy()])
```
 
INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:GPU:0', '/job:localhost/replica:0/task:0/device:GPU:1')

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-32-1a023ca8bc9a> in <module>
      5     # filepath = 'models/' + model_selection + '_' + model_number + '_all' + '.h5'
      6     filepath = 'models/' + model_selection + '_' + model_number + '.h5'
----> 7     model = load_model(filepath, compile=False)
      8 
      9     model.compile(optimizer=Adam(), 

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/save.py in load_model(filepath, custom_objects, compile)
    144   if (h5py is not None and (
    145       isinstance(filepath, h5py.File) or h5py.is_hdf5(filepath))):
--> 146     return hdf5_format.load_model_from_hdf5(filepath, custom_objects, compile)
    147 
    148   if isinstance(filepath, six.string_types):

/wsu/el7/pre-compiled/python/3.7/envs/tensorflow_gpuenv_v2/lib/python3.7/site-packages/tensorflow_core/python/keras/saving/hdf5_format.py in load_model_from_hdf5(filepath, custom_objects, compile)
    163     model_config = f.attrs.get('model_config')
    164     if model_config is None:
--> 165       raise ValueError('No model found in config file.')
    166     model_config = json.loads(model_config.decode('utf-8'))
    167     model = model_config_lib.model_from_config(model_config,

ValueError: No model found in config file.


**Saving as JSON or tensorflow _savedmodel_ format are equally ineffective. At this time the only fix I can find in order to be able to use a pretrained model, is to save only the weights:**

`model.save_weights('models/3_epoch_' + model_selection + '_' + model_number + '_weights' + '.h5')`

**then recompile the model from scratch and load the saved weights:**

```
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():

    model = Dual_ResNet_Atrous_Att_WST(input_shape=(HEIGHT, WIDTH, CHANNELS),
               mra_channels=MRACHANNELS,                          
               num_class=NUM_CLASS,
               ks1=KS1, ks2=KS2, ks3=KS3, 
               dl1=DL1, dl2=DL2, dl3=DL3,
               filters=NF,resblock1=NR1,
               r_filters=NFL, resblock2=NR2,
               dil_mode=DIL_MODE, 
               sp_dropout=DR1,re_dropout=DR2,
               mask_float=True,
               attention='wst',
               mask_threshold=0.5,                                         
               return_seq=True)

    model.compile(optimizer=Adam(), 
                  loss=tf.keras.losses.CategoricalCrossentropy(), 
                  metrics=[tf.keras.metrics.CategoricalAccuracy()])          

model.load_weights('models/best_' + model_selection + '_' + model_number + '_weights.h5') 

```

 
 


Discussion:
hello @dgattiwsu, thank you for reporting this. the first step is to narrow down the problem to a minimal (non-)working example. What happens if you remove all Keras layers except for Input and Scattering2D?
i think it comes down to a missing `get_config` method in `keras_frontend.py`
https://github.com/kymatio/kymatio/blob/master/kymatio/frontend/keras_frontend.py#L4

Here's some relevant doc from Keras: 
```python
class CustomLayer(keras.layers.Layer):
    def __init__(self, a):
        self.var = tf.Variable(a, name="var_a")

    def call(self, inputs, training=False):
        if training:
            return inputs * self.var
        else:
            return inputs

    def get_config(self):
        return {"a": self.var.numpy()}

    # There's actually no need to define `from_config` here, since returning
    # `cls(**config)` is the default behavior.
    @classmethod
    def from_config(cls, config):
        return cls(**config)
```
**Issue #637. 
Thanks for identifying the possible bug. Yes, the problem appears also with the example script from the current kymatio release. All attempt to save the model in different ways point to the same `get_config` method:**

```
import tensorflow as tf
from tensorflow.keras.models import Model, model_from_json, load_model
from tensorflow.keras.layers import Input, Flatten, Dense
import json 
from kymatio.keras import Scattering2D

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255., x_test / 255.

inputs = Input(shape=(28, 28))
x = Scattering2D(J=3, L=8)(inputs)
x = Flatten()(x)
x_out = Dense(10, activation='softmax')(x)
model = Model(inputs, x_out)

model.summary()

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(x_train[:10000], y_train[:10000], epochs=15,
          batch_size=64, validation_split=0.2)

model.evaluate(x_test, y_test)
```
_...
Epoch 15/15
125/125 [==============================] - 282s 2s/step - loss: 0.6652 - accuracy: 0.9266 - val_loss: 0.6579 - val_accuracy: 0.9100
313/313 [==============================] - 449s 1s/step - loss: 0.6451 - accuracy: 0.9271_

**Save architecture without weights as h5**
`model.save('model_arch.h5')`

_Traceback (most recent call last):

  File "/Users/dgatti/Documents/TEACHING/IBS_7140/IBS_7140_2018/ACTIVE_MATERIAL/TUTORIALS/PYTHON/Tutorial_PLOS/untitled1.py", line 87, in <module>
    model.save('model_arch.h5')

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 1979, in save
    signatures, options)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/save.py", line 131, in save_model
    model, filepath, overwrite, include_optimizer)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/hdf5_format.py", line 109, in save_model_to_hdf5
    model_metadata = saving_utils.model_metadata(model, include_optimizer)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/saving_utils.py", line 157, in model_metadata
    raise e

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/saving_utils.py", line 154, in model_metadata
    model_config['config'] = model.get_config()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py", line 598, in get_config
    return copy.deepcopy(get_network_config(self))

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py", line 1278, in get_network_config
    layer_config = serialize_layer_fn(layer)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/utils/generic_utils.py", line 250, in serialize_keras_object
    raise e

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/utils/generic_utils.py", line 245, in serialize_keras_object
    config = instance.get_config()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/base_layer.py", line 678, in get_config
    self.__class__.__name__)

NotImplementedError: Layer Scattering2D has arguments in `__init__` and therefore must override `get_config`._


**Save architecture + weights**
`model.save('model_all.h5',overwrite=True,include_optimizer=False,save_format='h5',signatures=None,options=None)`
 
_Traceback (most recent call last):

  File "/Users/dgatti/Documents/TEACHING/IBS_7140/IBS_7140_2018/ACTIVE_MATERIAL/TUTORIALS/PYTHON/Tutorial_PLOS/untitled1.py", line 92, in <module>
    overwrite=True,include_optimizer=False,save_format='h5',signatures=None,options=None)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 1979, in save
    signatures, options)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/save.py", line 131, in save_model
    model, filepath, overwrite, include_optimizer)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/hdf5_format.py", line 109, in save_model_to_hdf5
    model_metadata = saving_utils.model_metadata(model, include_optimizer)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/saving_utils.py", line 157, in model_metadata
    raise e

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/saving/saving_utils.py", line 154, in model_metadata
    model_config['config'] = model.get_config()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py", line 598, in get_config
    return copy.deepcopy(get_network_config(self))

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py", line 1278, in get_network_config
    layer_config = serialize_layer_fn(layer)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/utils/generic_utils.py", line 250, in serialize_keras_object
    raise e

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/utils/generic_utils.py", line 245, in serialize_keras_object
    config = instance.get_config()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/base_layer.py", line 678, in get_config
    self.__class__.__name__)

NotImplementedError: Layer Scattering2D has arguments in `__init__` and therefore must override `get_config`._

 
**Save architecture as json**
```
model_json = model.to_json()
with open('model.json', "w") as json_file:
    json_file.write(model_json) 
```
_Traceback (most recent call last):

  File "/Users/dgatti/Documents/TEACHING/IBS_7140/IBS_7140_2018/ACTIVE_MATERIAL/TUTORIALS/PYTHON/Tutorial_PLOS/untitled1.py", line 100, in <module>
    model_json = model.to_json()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 2254, in to_json
    model_config = self._updated_config()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py", line 2221, in _updated_config
    config = self.get_config()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py", line 598, in get_config
    return copy.deepcopy(get_network_config(self))

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/functional.py", line 1278, in get_network_config
    layer_config = serialize_layer_fn(layer)

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/utils/generic_utils.py", line 250, in serialize_keras_object
    raise e

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/utils/generic_utils.py", line 245, in serialize_keras_object
    config = instance.get_config()

  File "/Users/dgatti/venv_jupyter/lib/python3.6/site-packages/tensorflow/python/keras/engine/base_layer.py", line 678, in get_config
    self.__class__.__name__)

NotImplementedError: Layer Scattering2D has arguments in `__init__` and therefore must override `get_config`._


@dgattiwsu OK, so now the question is what the `get_config` method should return in our case. Parameters only (Q, J, etc.) or the filters psi and phi too?
@lostanlen  Thank you again for the time you are spending on this. Don't know enough of Keras (and even less of kymatio) to make a sensible suggestion on what dictionary to put under get config. In the meantime, a reasonable fix seems to upgrade to the latest tensorflow and to save in tensorflow SaveModel format. This works for the kymatio example script:

```
import tensorflow as tf
from tensorflow.keras.models import Model, model_from_json, load_model
from tensorflow.keras.layers import Input, Flatten, Dense
import json

import kymatio
from kymatio.keras import Scattering2D

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train / 255., x_test / 255.

inputs = Input(shape=(28, 28))
x = Scattering2D(J=3, L=8)(inputs)
x = Flatten()(x)
x_out = Dense(10, activation='softmax')(x)
model = Model(inputs, x_out)

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
model.fit(x_train[:500], y_train[:500], epochs=1,
          batch_size=64, validation_split=0.2)

model.evaluate(x_test[:500], y_test[:500])
```
16/16 [==============================] - 15s 949ms/step - loss: 2.2898 - accuracy: 0.2540


```
# Save as tensorflow SaveModel format
model.save('model_all')

# Reload as 'reconstructed_model'
reconstructed_model = load_model("model_all")

reconstructed_model.evaluate(x_test[:500], y_test[:500]) 
```
16/16 [==============================] - 19s 1s/step - loss: 2.2898 - accuracy: 0.2540
 
Although still fails (with a plethora of error messages) with the large model I work with.  
> OK, so now the question is what the get_config method should return in our case. Parameters only (Q, J, etc.) or the filters psi and phi too?

If I understand correctly, all we need to save are the parameters that later need to get passed into `__init__`. For `Scattering2D` that's `J`, `L`, `max_order`, and `pre_pad`.
@janden Right. I suspect that we'd also have to write a `from_config` method which itself calls the `Scattering2D` Keras constructor, so that the model could be loaded back after being stored on disk
Great this looks like you have successfully identified the problem and a solution to it.

We should get this fixed at the latest by the sprint in February
on it

Resolving patch:
diff --git a/kymatio/scattering1d/frontend/keras_frontend.py b/kymatio/scattering1d/frontend/keras_frontend.py
--- a/kymatio/scattering1d/frontend/keras_frontend.py
+++ b/kymatio/scattering1d/frontend/keras_frontend.py
@@ -27,5 +27,8 @@ def compute_output_shape(self, input_shape):
         output_shape = [input_shape[0], nc, ln]
         return tensor_shape.TensorShape(output_shape)
 
+    def get_config(self):
+        keys = ["J", "Q", "max_order", "oversampling"]
+        return {key: getattr(self, key) for key in keys}
 
 ScatteringKeras1D._document()
diff --git a/kymatio/scattering2d/frontend/keras_frontend.py b/kymatio/scattering2d/frontend/keras_frontend.py
--- a/kymatio/scattering2d/frontend/keras_frontend.py
+++ b/kymatio/scattering2d/frontend/keras_frontend.py
@@ -29,5 +29,8 @@ def compute_output_shape(self, input_shape):
         output_shape = [input_shape[0], nc, m0, m1]
         return tensor_shape.TensorShape(output_shape)
 
+    def get_config(self):
+        keys = ["J", "L", "max_order", "pre_pad"]
+        return {key: getattr(self, key) for key in keys}
 
 ScatteringKeras2D._document()


Test patch:
diff --git a/tests/scattering2d/test_keras_scattering2d.py b/tests/scattering2d/test_keras_scattering2d.py
--- a/tests/scattering2d/test_keras_scattering2d.py
+++ b/tests/scattering2d/test_keras_scattering2d.py
@@ -21,9 +21,10 @@ def test_Scattering2D():
     N = x.shape[3]
 
     inputs = Input(shape=(3, M, N))
-    scat = Scattering2D(J=J)(inputs)
+    sc_layer = Scattering2D(J=J)
+    sc_tensor = sc_layer(inputs)
 
-    model = Model(inputs, scat)
+    model = Model(inputs, sc_tensor)
     model.compile(optimizer='adam',
                   loss='sparse_categorical_crossentropy',
                   metrics=['accuracy'])
@@ -31,4 +32,8 @@ def test_Scattering2D():
     x = x
     S = S
     Sg = model.predict(x)
+    config = sc_layer.get_config()
+
     assert np.allclose(Sg, S)
+    assert config["J"] == J
+    assert Scattering2D.from_config(config).J == J