diff --git a/keras_core/layers/__init__.py b/keras_core/layers/__init__.py index bd402016f..a6030e29b 100644 --- a/keras_core/layers/__init__.py +++ b/keras_core/layers/__init__.py @@ -9,3 +9,4 @@ from keras_core.layers.regularization.activity_regularization import ( ) from keras_core.layers.regularization.dropout import Dropout from keras_core.layers.regularization.gaussian_dropout import GaussianDropout +from keras_core.layers.regularization.gaussian_noise import GaussianNoise diff --git a/keras_core/layers/regularization/dropout.py b/keras_core/layers/regularization/dropout.py index 42ed5d05b..e65d16260 100644 --- a/keras_core/layers/regularization/dropout.py +++ b/keras_core/layers/regularization/dropout.py @@ -42,7 +42,7 @@ class Dropout(layers.Layer): self, rate, noise_shape=None, seed=None, name=None, dtype=None ): super().__init__(name=name, dtype=dtype) - if isinstance(rate, (int, float)) and not 0 <= rate <= 1: + if not 0 <= rate <= 1: raise ValueError( f"Invalid value received for argument " "`rate`. Expected a float value between 0 and 1. " diff --git a/keras_core/layers/regularization/gaussian_dropout.py b/keras_core/layers/regularization/gaussian_dropout.py index ceb9118fc..bee22320e 100644 --- a/keras_core/layers/regularization/gaussian_dropout.py +++ b/keras_core/layers/regularization/gaussian_dropout.py @@ -24,11 +24,9 @@ class GaussianDropout(layers.Layer): training mode (adding dropout) or in inference mode (doing nothing). """ - def __init__( - self, rate, noise_shape=None, seed=None, name=None, dtype=None - ): + def __init__(self, rate, seed=None, name=None, dtype=None): super().__init__(name=name, dtype=dtype) - if isinstance(rate, (int, float)) and not 0 <= rate <= 1: + if not 0 <= rate <= 1: raise ValueError( f"Invalid value received for argument " "`rate`. Expected a float value between 0 and 1. " @@ -36,7 +34,6 @@ class GaussianDropout(layers.Layer): ) self.rate = rate self.seed = seed - self.noise_shape = noise_shape self.seed_generator = backend.random.SeedGenerator(seed) self.supports_masking = True diff --git a/keras_core/layers/regularization/gaussian_dropout_test.py b/keras_core/layers/regularization/gaussian_dropout_test.py index 03958cb65..18831af88 100644 --- a/keras_core/layers/regularization/gaussian_dropout_test.py +++ b/keras_core/layers/regularization/gaussian_dropout_test.py @@ -7,7 +7,7 @@ from keras_core import testing class GaussianDropoutTest(testing.TestCase): def test_gaussian_dropout_basics(self): self.run_layer_test( - layers.Dropout, + layers.GaussianDropout, init_kwargs={ "rate": 0.2, }, diff --git a/keras_core/layers/regularization/gaussian_noise.py b/keras_core/layers/regularization/gaussian_noise.py new file mode 100644 index 000000000..66466cd0e --- /dev/null +++ b/keras_core/layers/regularization/gaussian_noise.py @@ -0,0 +1,60 @@ +from keras_core import backend +from keras_core import layers +from keras_core import operations as ops +from keras_core.api_export import keras_core_export + + +@keras_core_export("keras_core.layers.GaussianNoise") +class GaussianNoise(layers.Layer): + """Apply additive zero-centered Gaussian noise. + + This is useful to mitigate overfitting + (you could see it as a form of random data augmentation). + Gaussian Noise (GS) is a natural choice as corruption process + for real valued inputs. + + As it is a regularization layer, it is only active at training time. + + Args: + stddev: Float, standard deviation of the noise distribution. + seed: Integer, optional random seed to enable deterministic behavior. + + Call arguments: + inputs: Input tensor (of any rank). + training: Python boolean indicating whether the layer should behave in + training mode (adding noise) or in inference mode (doing nothing). + """ + + def __init__(self, stddev, seed=None, name=None, dtype=None): + super().__init__(name=name, dtype=dtype) + if not 0 <= stddev <= 1: + raise ValueError( + f"Invalid value received for argument " + "`stddev`. Expected a float value between 0 and 1. " + f"Received: stddev={stddev}" + ) + self.stddev = stddev + self.seed = seed + self.seed_generator = backend.random.SeedGenerator(seed) + self.supports_masking = True + + def call(self, inputs, training=False): + if training and self.stddev > 0: + return inputs + backend.random.normal( + shape=ops.shape(inputs), + mean=0.0, + stddev=self.stddev, + seed=self.seed_generator, + ) + return inputs + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + base_config = super().get_config() + config = { + "stddev": self.stddev, + "seed": self.seed, + } + return {**base_config, **config} diff --git a/keras_core/layers/regularization/gaussian_noise_test.py b/keras_core/layers/regularization/gaussian_noise_test.py new file mode 100644 index 000000000..b89e0ab95 --- /dev/null +++ b/keras_core/layers/regularization/gaussian_noise_test.py @@ -0,0 +1,27 @@ +import numpy as np + +from keras_core import layers +from keras_core import testing + + +class GaussianNoiseTest(testing.TestCase): + def test_gaussian_noise_basics(self): + self.run_layer_test( + layers.GaussianNoise, + init_kwargs={ + "stddev": 0.2, + }, + input_shape=(2, 3), + expected_output_shape=(2, 3), + expected_num_trainable_weights=0, + expected_num_non_trainable_weights=0, + expected_num_seed_generators=1, + expected_num_losses=0, + supports_masking=True, + ) + + def test_gaussian_noise_correctness(self): + inputs = np.ones((20, 500)) + layer = layers.GaussianNoise(0.3, seed=1337) + outputs = layer(inputs, training=True) + self.assertAllClose(np.std(outputs), 0.3, atol=0.02)