Add more merging layers (#71)

* add base merge layer

* format docstrings

* add  layer

* add test cases for  layer

* Add import for  layer

* fix build function

* add dynamic and static tests

* fix pytest import

* fix pytest decorator

* remove batch size from dynamic shape test

* fix keras reference

* refactor test class

* fix tf tests, and linting issues

* add subtract layer

* add tests for subtract layer

* fix linting issues

* add average layer

* add maximum layer

* dd minimum layer

* add multiply layer

* add tests for average, minimum, maximum, and multiply layers
This commit is contained in:
Aakash Kumar Nain 2023-05-03 10:57:43 +05:30 committed by Francois Chollet
parent bb6dc0fbf4
commit 50e106b56c
6 changed files with 650 additions and 0 deletions

@ -6,6 +6,14 @@ from keras_core.layers.core.input_layer import InputLayer
from keras_core.layers.layer import Layer
from keras_core.layers.merging.add import Add
from keras_core.layers.merging.add import add
from keras_core.layers.merging.average import Average
from keras_core.layers.merging.average import average
from keras_core.layers.merging.maximum import Maximum
from keras_core.layers.merging.maximum import maximum
from keras_core.layers.merging.minimum import Minimum
from keras_core.layers.merging.minimum import minimum
from keras_core.layers.merging.multiply import Multiply
from keras_core.layers.merging.multiply import multiply
from keras_core.layers.merging.subtract import Subtract
from keras_core.layers.merging.subtract import subtract
from keras_core.layers.pooling.average_pooling1d import AveragePooling1D

@ -0,0 +1,69 @@
from keras_core.api_export import keras_core_export
from keras_core.layers.merging.base_merge import Merge
@keras_core_export("keras_core.layers.Average")
class Average(Merge):
"""Averages a list of inputs element-wise..
It takes as input a list of tensors, all of the same shape,
and returns a single tensor (also of the same shape).
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.Average()([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> # equivalent to `y = keras_core.layers.average([x1, x2])`
>>> y = keras_core.layers.Average()([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
def _merge_function(self, inputs):
output = inputs[0]
for i in range(1, len(inputs)):
output += inputs[i]
return output / len(inputs)
@keras_core_export("keras_core.layers.average")
def average(inputs, **kwargs):
"""Functional interface to the `keras_core.layers.Average` layer.
Args:
inputs: A list of input tensors , all of the same shape.
**kwargs: Standard layer keyword arguments.
Returns:
A tensor as the element-wise product of the inputs with the same
shape as the inputs.
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.average([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> y = keras_core.layers.average([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
return Average(**kwargs)(inputs)

@ -0,0 +1,70 @@
from keras_core import operations as ops
from keras_core.api_export import keras_core_export
from keras_core.layers.merging.base_merge import Merge
@keras_core_export("keras_core.layers.Maximum")
class Maximum(Merge):
"""Computes element-wise maximum on a list of inputs.
It takes as input a list of tensors, all of the same shape,
and returns a single tensor (also of the same shape).
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.Maximum()([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> # equivalent to `y = keras_core.layers.maximum([x1, x2])`
>>> y = keras_core.layers.Maximum()([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
def _merge_function(self, inputs):
output = inputs[0]
for i in range(1, len(inputs)):
output = ops.maximum(output, inputs[i])
return output
@keras_core_export("keras_core.layers.maximum")
def maximum(inputs, **kwargs):
"""Functional interface to the `keras_core.layers.Maximum` layer.
Args:
inputs: A list of input tensors , all of the same shape.
**kwargs: Standard layer keyword arguments.
Returns:
A tensor as the element-wise product of the inputs with the same
shape as the inputs.
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.maximum([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> y = keras_core.layers.maximum([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
return Maximum(**kwargs)(inputs)

@ -199,3 +199,367 @@ class MergingLayersTest(testing.TestCase):
ValueError, "layer should be called on exactly 2 inputs"
):
layers.Subtract()([input_1])
def test_minimum_basic(self):
self.run_layer_test(
layers.Minimum,
init_kwargs={},
input_shape=[(2, 3), (2, 3)],
expected_output_shape=(2, 3),
expected_num_trainable_weights=0,
expected_num_non_trainable_weights=0,
expected_num_seed_generators=0,
expected_num_losses=0,
supports_masking=True,
)
@pytest.mark.skipif(
not backend.DYNAMIC_SHAPES_OK,
reason="Backend does not support dynamic shapes.",
)
def test_minimum_correctness_dynamic(self):
x1 = np.random.rand(2, 4, 5)
x2 = np.random.rand(2, 4, 5)
x3 = ops.minimum(x1, x2)
input_1 = layers.Input(shape=(4, 5))
input_2 = layers.Input(shape=(4, 5))
merge_layer = layers.Minimum()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (2, 4, 5))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_minimum_correctness_static(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
x2 = np.random.rand(batch_size, *shape)
x3 = ops.minimum(x1, x2)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Minimum()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (batch_size, *shape))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_minimum_errors(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Minimum()
with self.assertRaisesRegex(ValueError, "`mask` should be a list."):
merge_layer.compute_mask([input_1, input_2], x1)
with self.assertRaisesRegex(ValueError, "`inputs` should be a list."):
merge_layer.compute_mask(input_1, [None, None])
with self.assertRaisesRegex(
ValueError, " should have the same length."
):
merge_layer.compute_mask([input_1, input_2], [None])
def test_maximum_basic(self):
self.run_layer_test(
layers.Maximum,
init_kwargs={},
input_shape=[(2, 3), (2, 3)],
expected_output_shape=(2, 3),
expected_num_trainable_weights=0,
expected_num_non_trainable_weights=0,
expected_num_seed_generators=0,
expected_num_losses=0,
supports_masking=True,
)
@pytest.mark.skipif(
not backend.DYNAMIC_SHAPES_OK,
reason="Backend does not support dynamic shapes.",
)
def test_maximum_correctness_dynamic(self):
x1 = np.random.rand(2, 4, 5)
x2 = np.random.rand(2, 4, 5)
x3 = ops.maximum(x1, x2)
input_1 = layers.Input(shape=(4, 5))
input_2 = layers.Input(shape=(4, 5))
merge_layer = layers.Maximum()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (2, 4, 5))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_maximum_correctness_static(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
x2 = np.random.rand(batch_size, *shape)
x3 = ops.maximum(x1, x2)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Maximum()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (batch_size, *shape))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_maximum_errors(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Maximum()
with self.assertRaisesRegex(ValueError, "`mask` should be a list."):
merge_layer.compute_mask([input_1, input_2], x1)
with self.assertRaisesRegex(ValueError, "`inputs` should be a list."):
merge_layer.compute_mask(input_1, [None, None])
with self.assertRaisesRegex(
ValueError, " should have the same length."
):
merge_layer.compute_mask([input_1, input_2], [None])
def test_multiply_basic(self):
self.run_layer_test(
layers.Multiply,
init_kwargs={},
input_shape=[(2, 3), (2, 3)],
expected_output_shape=(2, 3),
expected_num_trainable_weights=0,
expected_num_non_trainable_weights=0,
expected_num_seed_generators=0,
expected_num_losses=0,
supports_masking=True,
)
@pytest.mark.skipif(
not backend.DYNAMIC_SHAPES_OK,
reason="Backend does not support dynamic shapes.",
)
def test_multiply_correctness_dynamic(self):
x1 = np.random.rand(2, 4, 5)
x2 = np.random.rand(2, 4, 5)
x3 = ops.convert_to_tensor(x1 * x2)
input_1 = layers.Input(shape=(4, 5))
input_2 = layers.Input(shape=(4, 5))
merge_layer = layers.Multiply()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (2, 4, 5))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_multiply_correctness_static(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
x2 = np.random.rand(batch_size, *shape)
x3 = ops.convert_to_tensor(x1 * x2)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Multiply()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (batch_size, *shape))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_multiply_errors(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Multiply()
with self.assertRaisesRegex(ValueError, "`mask` should be a list."):
merge_layer.compute_mask([input_1, input_2], x1)
with self.assertRaisesRegex(ValueError, "`inputs` should be a list."):
merge_layer.compute_mask(input_1, [None, None])
with self.assertRaisesRegex(
ValueError, " should have the same length."
):
merge_layer.compute_mask([input_1, input_2], [None])
def test_average_basic(self):
self.run_layer_test(
layers.Average,
init_kwargs={},
input_shape=[(2, 3), (2, 3)],
expected_output_shape=(2, 3),
expected_num_trainable_weights=0,
expected_num_non_trainable_weights=0,
expected_num_seed_generators=0,
expected_num_losses=0,
supports_masking=True,
)
@pytest.mark.skipif(
not backend.DYNAMIC_SHAPES_OK,
reason="Backend does not support dynamic shapes.",
)
def test_average_correctness_dynamic(self):
x1 = np.random.rand(2, 4, 5)
x2 = np.random.rand(2, 4, 5)
x3 = ops.average(np.array([x1, x2]), axis=0)
input_1 = layers.Input(shape=(4, 5))
input_2 = layers.Input(shape=(4, 5))
merge_layer = layers.Average()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (2, 4, 5))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_average_correctness_static(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
x2 = np.random.rand(batch_size, *shape)
x3 = ops.average(np.array([x1, x2]), axis=0)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Average()
out = merge_layer([input_1, input_2])
model = models.Model([input_1, input_2], out)
res = model([x1, x2])
self.assertEqual(res.shape, (batch_size, *shape))
self.assertAllClose(res, x3, atol=1e-4)
self.assertIsNone(
merge_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
merge_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)
def test_average_errors(self):
batch_size = 2
shape = (4, 5)
x1 = np.random.rand(batch_size, *shape)
input_1 = layers.Input(shape=shape, batch_size=batch_size)
input_2 = layers.Input(shape=shape, batch_size=batch_size)
merge_layer = layers.Average()
with self.assertRaisesRegex(ValueError, "`mask` should be a list."):
merge_layer.compute_mask([input_1, input_2], x1)
with self.assertRaisesRegex(ValueError, "`inputs` should be a list."):
merge_layer.compute_mask(input_1, [None, None])
with self.assertRaisesRegex(
ValueError, " should have the same length."
):
merge_layer.compute_mask([input_1, input_2], [None])

@ -0,0 +1,70 @@
from keras_core import operations as ops
from keras_core.api_export import keras_core_export
from keras_core.layers.merging.base_merge import Merge
@keras_core_export("keras_core.layers.Minimum")
class Minimum(Merge):
"""Computes elementwise minimum on a list of inputs.
It takes as input a list of tensors, all of the same shape,
and returns a single tensor (also of the same shape).
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.Minimum()([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> # equivalent to `y = keras_core.layers.minimum([x1, x2])`
>>> y = keras_core.layers.Minimum()([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
def _merge_function(self, inputs):
output = inputs[0]
for i in range(1, len(inputs)):
output = ops.minimum(output, inputs[i])
return output
@keras_core_export("keras_core.layers.minimum")
def minimum(inputs, **kwargs):
"""Functional interface to the `keras_core.layers.Minimum` layer.
Args:
inputs: A list of input tensors , all of the same shape.
**kwargs: Standard layer keyword arguments.
Returns:
A tensor as the elementwise product of the inputs with the same
shape as the inputs.
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.minimum([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> y = keras_core.layers.minimum([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
return Minimum(**kwargs)(inputs)

@ -0,0 +1,69 @@
from keras_core.api_export import keras_core_export
from keras_core.layers.merging.base_merge import Merge
@keras_core_export("keras_core.layers.Multiply")
class Multiply(Merge):
"""Performs elementwise multiplication.
It takes as input a list of tensors, all of the same shape,
and returns a single tensor (also of the same shape).
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.Multiply()([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> # equivalent to `y = keras_core.layers.multiply([x1, x2])`
>>> y = keras_core.layers.Multiply()([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
def _merge_function(self, inputs):
output = inputs[0]
for i in range(1, len(inputs)):
output = output * inputs[i]
return output
@keras_core_export("keras_core.layers.multiply")
def multiply(inputs, **kwargs):
"""Functional interface to the `keras_core.layers.Multiply` layer.
Args:
inputs: A list of input tensors , all of the same shape.
**kwargs: Standard layer keyword arguments.
Returns:
A tensor as the elementwise product of the inputs with the same
shape as the inputs.
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.multiply([x1, x2])
Usage in a Keras model:
>>> input1 = keras_core.layers.Input(shape=(16,))
>>> x1 = keras_core.layers.Dense(8, activation='relu')(input1)
>>> input2 = keras_core.layers.Input(shape=(32,))
>>> x2 = keras_core.layers.Dense(8, activation='relu')(input2)
>>> y = keras_core.layers.multiply([x1, x2])
>>> out = keras_core.layers.Dense(4)(y)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
return Multiply(**kwargs)(inputs)