Skip to content

Commit

Permalink
Add subtract layer (keras-team#69)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
AakashKumarNain authored May 2, 2023
1 parent 65bb03c commit b1b1a4b
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 1 deletion.
2 changes: 2 additions & 0 deletions keras_core/layers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
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.subtract import Subtract
from keras_core.layers.merging.subtract import subtract
from keras_core.layers.regularization.activity_regularization import (
ActivityRegularization,
)
Expand Down
102 changes: 101 additions & 1 deletion keras_core/layers/merging/merging_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_add_basic(self):

@pytest.mark.skipif(
not backend.DYNAMIC_SHAPES_OK,
reason="Dynamic shapes are only supported in TensorFlow backend.",
reason="Backend does not support dynamic shapes.",
)
def test_add_correctness_dynamic(self):
x1 = np.random.rand(2, 4, 5)
Expand Down Expand Up @@ -99,3 +99,103 @@ def test_add_errors(self):
ValueError, " should have the same length."
):
add_layer.compute_mask([input_1, input_2], [None])

def test_subtract_basic(self):
self.run_layer_test(
layers.Subtract,
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_subtract_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))
subtract_layer = layers.Subtract()
out = subtract_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(
subtract_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
subtract_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)

def test_subtract_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)
subtract_layer = layers.Subtract()
out = subtract_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(
subtract_layer.compute_mask([input_1, input_2], [None, None])
)
self.assertTrue(
np.all(
subtract_layer.compute_mask(
[input_1, input_2],
[backend.Variable(x1), backend.Variable(x2)],
)
)
)

def test_subtract_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)
input_3 = layers.Input(shape=shape, batch_size=batch_size)
subtract_layer = layers.Subtract()

with self.assertRaisesRegex(ValueError, "`mask` should be a list."):
subtract_layer.compute_mask([input_1, input_2], x1)

with self.assertRaisesRegex(ValueError, "`inputs` should be a list."):
subtract_layer.compute_mask(input_1, [None, None])

with self.assertRaisesRegex(
ValueError, " should have the same length."
):
subtract_layer.compute_mask([input_1, input_2], [None])
with self.assertRaisesRegex(
ValueError, "layer should be called on exactly 2 inputs"
):
layers.Subtract()([input_1, input_2, input_3])
with self.assertRaisesRegex(
ValueError, "layer should be called on exactly 2 inputs"
):
layers.Subtract()([input_1])
81 changes: 81 additions & 0 deletions keras_core/layers/merging/subtract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from keras_core.api_export import keras_core_export
from keras_core.layers.merging.base_merge import Merge


@keras_core_export("keras_core.layers.Subtract")
class Subtract(Merge):
"""Performs elementwise subtraction.
It takes as input a list of tensors of size 2 both of the
same shape, and returns a single tensor (inputs[0] - inputs[1])
of same shape.
Examples:
>>> input_shape = (2, 3, 4)
>>> x1 = np.random.rand(*input_shape)
>>> x2 = np.random.rand(*input_shape)
>>> y = keras_core.layers.Subtract()([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 `subtracted = keras_core.layers.subtract([x1, x2])`
>>> subtracted = keras_core.layers.Subtract()([x1, x2])
>>> out = keras_core.layers.Dense(4)(subtracted)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""

def build(self, input_shape):
super().build(input_shape)
if len(input_shape) != 2:
raise ValueError(
"A `Subtract` layer should be called on exactly 2 inputs. "
f"Received: input_shape={input_shape}"
)

def _merge_function(self, inputs):
if len(inputs) != 2:
raise ValueError(
"A `Subtract` layer should be called on exactly 2 inputs. "
f"Received: inputs={inputs}"
)
return inputs[0] - inputs[1]


@keras_core_export("keras_core.layers.subtract")
def subtract(inputs, **kwargs):
"""Functional interface to the `keras_core.layers.Subtract` layer.
Args:
inputs: A list of input tensors of size 2, each tensor of
the same shape.
**kwargs: Standard layer keyword arguments.
Returns:
A tensor as the difference of the inputs. It has 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.subtract([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)
>>> subtracted = keras_core.layers.subtract([x1, x2])
>>> out = keras_core.layers.Dense(4)(subtracted)
>>> model = keras_core.models.Model(inputs=[input1, input2], outputs=out)
"""
return Subtract(**kwargs)(inputs)

0 comments on commit b1b1a4b

Please sign in to comment.