Inspired by @Srihari Humbarwadi, I find a way to implement a complicated regularized that involves:
- add a trainable parameter for the regularizer loss
- custom calculation between weights in different layers
The idea is to construct a subclass model:
class Pseudo_Model(Model):
def __init__(self, **kwargs):
super(Pseudo_Model, self).__init__(**kwargs)
self.dense1 = Dense(16)
self.dense2 = Dense(4)
self.dense3 = Dense(2)
self.a = tf.Variable(shape=(1,), initial_value=tf.ones(shape=(1,)))
def call(self, inputs, training=True, mask=None):
x = self.dense1(inputs)
x = self.dense2(x)
x = self.dense3(x)
return x
The model is built through:
sub_model = Pseudo_Model(name='sub_model')
inputs = Input(shape=(32,))
outputs = sub_model(inputs)
model = Model(inputs, outputs)
model.summary()
model.get_layer('sub_model').summary()
The structure of the model:
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 32)] 0
_________________________________________________________________
sub_model (Pseudo_Model) (None, 2) 607
=================================================================
Total params: 607
Trainable params: 607
Non-trainable params: 0
_________________________________________________________________
Model: "sub_model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 16) 528
_________________________________________________________________
dense_1 (Dense) (None, 4) 68
_________________________________________________________________
dense_2 (Dense) (None, 2) 10
=================================================================
Total params: 607
Trainable params: 607
Non-trainable params: 0
_________________________________________________________________
Then define the loss function as @Srihari Humbarwadi has mentioned, only to add a new trainable parameter a:
def custom_loss(weight_a, weight_b, a):
def _custom_loss():
# This can include any arbitrary logic
loss = a * tf.norm(weight_a) + tf.norm(weight_b)
return loss
return _custom_loss
The loss is added to the model by add_loss() API:
a_ = model.get_layer('sub_model').a
weighta = model.get_layer('sub_model').layers[0].kernel
weightb = model.get_layer('sub_model').layers[1].kernel
model.get_layer('sub_model').add_loss(custom_loss(weighta, weightb, a_))
print(model.losses)
#[<tf.Tensor: id=116, shape=(1,), dtype=float32, numpy=array([7.2659254], dtype=float32)>]
Then I create a fake dataset to test it:
fake_data = np.random.rand(1000, 32)
fake_labels = np.random.rand(1000, 2)
model.compile(optimizer=tf.keras.optimizers.SGD(), loss='mse')
model.fit(x=fake_data, y=fake_labels, epochs=5)
print(model.get_layer(name='sub_model').a)
As you can see, the variables and loss are being updated:
Train on 1000 samples
Epoch 1/5
2020-06-19 19:21:02.475464: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library cublas64_100.dll
1000/1000 - 1s - loss: 3.9039
Epoch 2/5
1000/1000 - 0s - loss: -3.0905e+00
Epoch 3/5
1000/1000 - 0s - loss: -1.2103e+01
Epoch 4/5
1000/1000 - 0s - loss: -2.6855e+01
Epoch 5/5
1000/1000 - 0s - loss: -5.3408e+01
<tf.Variable 'Variable:0' shape=(1,) dtype=float32, numpy=array([-8.13609], dtype=float32)>
Process finished with exit code 0
But still, this is a really tricky method. I don't know if there is a more elegant and stable way to achieve the same function.