From 04a876b679368d8e8df0603cdbf5a8d2012c7e9c Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Fri, 25 Jul 2025 11:44:06 -0700 Subject: [PATCH 01/13] fix discreatization layer --- .../layers/preprocessing/discretization.py | 2 ++ .../preprocessing/discretization_test.py | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index 8c771a64a058..fc8af7bbf9ec 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -229,6 +229,8 @@ def call(self, inputs): "start using the `Discretization` layer." ) + if self.backend.backend() == "tensorflow": + inputs = self.backend.convert_to_tensor(inputs) indices = self.backend.numpy.digitize(inputs, self.bin_boundaries) return numerical_utils.encode_categorical_inputs( indices, diff --git a/keras/src/layers/preprocessing/discretization_test.py b/keras/src/layers/preprocessing/discretization_test.py index 500c6e9ca039..049b86faebb3 100644 --- a/keras/src/layers/preprocessing/discretization_test.py +++ b/keras/src/layers/preprocessing/discretization_test.py @@ -205,3 +205,32 @@ def test_call_before_adapt_raises(self): layer = layers.Discretization(num_bins=3) with self.assertRaisesRegex(ValueError, "You need .* call .*adapt"): layer([[0.1, 0.8, 0.9]]) + + +def test_discretization_eager_vs_graph(): + import os + + os.environ["KERAS_BACKEND"] = "tensorflow" + import tensorflow as tf + + import keras + + layer = keras.layers.Discretization( + bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], + name="bucket", + output_mode="int", + ) + + x = tf.constant([[0.0, 0.15, 0.21, 0.3], [0.0, 0.17, 0.451, 7.8]]) + inputs = keras.layers.Input(name="inp", dtype="float32", shape=(4,)) + model_output = layer(inputs) + model = keras.models.Model(inputs=[inputs], outputs=[model_output]) + + print("Eager mode (layer(x)):") + print(layer(x).numpy()) + + print("Model call (model(x)):") + print(model(x).numpy()) + + print("Model predict (model.predict(x)):") + print(model.predict(x)) From c6ce008bd63ca044592d714fcc0ef7bf53fb037a Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Fri, 25 Jul 2025 12:08:47 -0700 Subject: [PATCH 02/13] fix dscreatization error --- keras/src/layers/preprocessing/discretization.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index fc8af7bbf9ec..f6bea75421aa 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -96,7 +96,7 @@ def __init__( name=None, ): if dtype is None: - dtype = "int64" if output_mode == "int" else backend.floatx() + dtype = "float32" super().__init__(name=name, dtype=dtype) @@ -213,7 +213,8 @@ def reset_state(self): self.summary = np.array([[], []], dtype="float32") def compute_output_spec(self, inputs): - return backend.KerasTensor(shape=inputs.shape, dtype=self.compute_dtype) + output_dtype = "int64" if self.output_mode == "int" else self.compute_dtype + return backend.KerasTensor(shape=inputs.shape, dtype=output_dtype) def load_own_variables(self, store): if len(store) == 1: @@ -229,9 +230,9 @@ def call(self, inputs): "start using the `Discretization` layer." ) - if self.backend.backend() == "tensorflow": - inputs = self.backend.convert_to_tensor(inputs) + # Use the backend's digitize function for all backends indices = self.backend.numpy.digitize(inputs, self.bin_boundaries) + return numerical_utils.encode_categorical_inputs( indices, output_mode=self.output_mode, From b19df6e6f57166e8454d0593ecc5f931c2f0620a Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Fri, 25 Jul 2025 12:15:50 -0700 Subject: [PATCH 03/13] update test --- .../preprocessing/discretization_test.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/keras/src/layers/preprocessing/discretization_test.py b/keras/src/layers/preprocessing/discretization_test.py index 049b86faebb3..89deb62e83b2 100644 --- a/keras/src/layers/preprocessing/discretization_test.py +++ b/keras/src/layers/preprocessing/discretization_test.py @@ -5,6 +5,7 @@ from absl.testing import parameterized from tensorflow import data as tf_data +import keras from keras.src import backend from keras.src import layers from keras.src import models @@ -206,6 +207,24 @@ def test_call_before_adapt_raises(self): with self.assertRaisesRegex(ValueError, "You need .* call .*adapt"): layer([[0.1, 0.8, 0.9]]) + @pytest.mark.skipif( + backend.backend() != "tensorflow", reason="test for tf backend" + ) + def test_discretization_eager_vs_graph(self): + layer = keras.layers.Discretization( + bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], + name="bucket", + output_mode="int", + ) + + x = np.array([[0.0, 0.15, 0.21, 0.3], [0.0, 0.17, 0.451, 7.8]]) + inputs = keras.layers.Input(name="inp", dtype="float32", shape=(4,)) + model_output = layer(inputs) + model = keras.models.Model(inputs=[inputs], outputs=[model_output]) + eager_out = model(x).numpy() + graph_out = model.predict(x) + self.assertAllClose(eager_out, graph_out) + def test_discretization_eager_vs_graph(): import os @@ -234,3 +253,7 @@ def test_discretization_eager_vs_graph(): print("Model predict (model.predict(x)):") print(model.predict(x)) + + +if __name__ == "__main__": + test_discretization_eager_vs_graph() From c66a616ec17a6f2701cb91a475a0c3c16d0c5f87 Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Fri, 25 Jul 2025 12:17:30 -0700 Subject: [PATCH 04/13] fix tests --- .../preprocessing/discretization_test.py | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization_test.py b/keras/src/layers/preprocessing/discretization_test.py index 89deb62e83b2..8be3f1ba0bfa 100644 --- a/keras/src/layers/preprocessing/discretization_test.py +++ b/keras/src/layers/preprocessing/discretization_test.py @@ -224,36 +224,3 @@ def test_discretization_eager_vs_graph(self): eager_out = model(x).numpy() graph_out = model.predict(x) self.assertAllClose(eager_out, graph_out) - - -def test_discretization_eager_vs_graph(): - import os - - os.environ["KERAS_BACKEND"] = "tensorflow" - import tensorflow as tf - - import keras - - layer = keras.layers.Discretization( - bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], - name="bucket", - output_mode="int", - ) - - x = tf.constant([[0.0, 0.15, 0.21, 0.3], [0.0, 0.17, 0.451, 7.8]]) - inputs = keras.layers.Input(name="inp", dtype="float32", shape=(4,)) - model_output = layer(inputs) - model = keras.models.Model(inputs=[inputs], outputs=[model_output]) - - print("Eager mode (layer(x)):") - print(layer(x).numpy()) - - print("Model call (model(x)):") - print(model(x).numpy()) - - print("Model predict (model.predict(x)):") - print(model.predict(x)) - - -if __name__ == "__main__": - test_discretization_eager_vs_graph() From 795c3dea85f85d6fc26595386290b1844bd63fd0 Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Fri, 25 Jul 2025 12:24:36 -0700 Subject: [PATCH 05/13] code reformat --- keras/src/layers/preprocessing/discretization.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index f6bea75421aa..9258a775cf36 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -213,7 +213,9 @@ def reset_state(self): self.summary = np.array([[], []], dtype="float32") def compute_output_spec(self, inputs): - output_dtype = "int64" if self.output_mode == "int" else self.compute_dtype + output_dtype = ( + "int64" if self.output_mode == "int" else self.compute_dtype + ) return backend.KerasTensor(shape=inputs.shape, dtype=output_dtype) def load_own_variables(self, store): From 04c002b3f65fa1b015568fcef3c1fc185163f668 Mon Sep 17 00:00:00 2001 From: divyashreepathihalli Date: Fri, 25 Jul 2025 19:58:51 +0000 Subject: [PATCH 06/13] c --- .../preprocessing/discretization_test.py | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization_test.py b/keras/src/layers/preprocessing/discretization_test.py index 8be3f1ba0bfa..2bdc02a73a4f 100644 --- a/keras/src/layers/preprocessing/discretization_test.py +++ b/keras/src/layers/preprocessing/discretization_test.py @@ -5,7 +5,6 @@ from absl.testing import parameterized from tensorflow import data as tf_data -import keras from keras.src import backend from keras.src import layers from keras.src import models @@ -205,22 +204,4 @@ def test_init_num_bins_and_bin_boundaries_raises(self): def test_call_before_adapt_raises(self): layer = layers.Discretization(num_bins=3) with self.assertRaisesRegex(ValueError, "You need .* call .*adapt"): - layer([[0.1, 0.8, 0.9]]) - - @pytest.mark.skipif( - backend.backend() != "tensorflow", reason="test for tf backend" - ) - def test_discretization_eager_vs_graph(self): - layer = keras.layers.Discretization( - bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], - name="bucket", - output_mode="int", - ) - - x = np.array([[0.0, 0.15, 0.21, 0.3], [0.0, 0.17, 0.451, 7.8]]) - inputs = keras.layers.Input(name="inp", dtype="float32", shape=(4,)) - model_output = layer(inputs) - model = keras.models.Model(inputs=[inputs], outputs=[model_output]) - eager_out = model(x).numpy() - graph_out = model.predict(x) - self.assertAllClose(eager_out, graph_out) + layer([[0.1, 0.8, 0.9]]) \ No newline at end of file From 4287f115792d3d3d3c4628115902643917534ae3 Mon Sep 17 00:00:00 2001 From: divyashreepathihalli Date: Fri, 25 Jul 2025 20:00:19 +0000 Subject: [PATCH 07/13] code reformat --- keras/src/layers/preprocessing/discretization_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keras/src/layers/preprocessing/discretization_test.py b/keras/src/layers/preprocessing/discretization_test.py index 2bdc02a73a4f..500c6e9ca039 100644 --- a/keras/src/layers/preprocessing/discretization_test.py +++ b/keras/src/layers/preprocessing/discretization_test.py @@ -204,4 +204,4 @@ def test_init_num_bins_and_bin_boundaries_raises(self): def test_call_before_adapt_raises(self): layer = layers.Discretization(num_bins=3) with self.assertRaisesRegex(ValueError, "You need .* call .*adapt"): - layer([[0.1, 0.8, 0.9]]) \ No newline at end of file + layer([[0.1, 0.8, 0.9]]) From 53a5e259d2d4806cb38eb70511ae466f2a6cdab5 Mon Sep 17 00:00:00 2001 From: divyashreepathihalli Date: Fri, 25 Jul 2025 21:42:40 +0000 Subject: [PATCH 08/13] default dtype --- keras/src/layers/preprocessing/discretization.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index 9258a775cf36..cfbb282fed55 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -92,12 +92,9 @@ def __init__( epsilon=0.01, output_mode="int", sparse=False, - dtype=None, + dtype="int64", # because default output mode is int name=None, ): - if dtype is None: - dtype = "float32" - super().__init__(name=name, dtype=dtype) if sparse and not backend.SUPPORTS_SPARSE_TENSORS: From 23444dc2ee002b6ed16e7ef8c9b3af7af7a6dca1 Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Sun, 27 Jul 2025 18:44:18 -0700 Subject: [PATCH 09/13] Update discretization.py --- keras/src/layers/preprocessing/discretization.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index cfbb282fed55..bb4b756c39f9 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -92,9 +92,12 @@ def __init__( epsilon=0.01, output_mode="int", sparse=False, - dtype="int64", # because default output mode is int + dtype=None, name=None, ): + if dtype is None: + dtype = "int64" if output_mode == "int" else backend.floatx() + super().__init__(name=name, dtype=dtype) if sparse and not backend.SUPPORTS_SPARSE_TENSORS: From 3241db001ec9f5728fe80b22803d334f58e7432f Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Sun, 27 Jul 2025 18:45:10 -0700 Subject: [PATCH 10/13] Update discretization.py --- keras/src/layers/preprocessing/discretization.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index bb4b756c39f9..eb3bcca4f556 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -97,7 +97,6 @@ def __init__( ): if dtype is None: dtype = "int64" if output_mode == "int" else backend.floatx() - super().__init__(name=name, dtype=dtype) if sparse and not backend.SUPPORTS_SPARSE_TENSORS: @@ -232,9 +231,7 @@ def call(self, inputs): "start using the `Discretization` layer." ) - # Use the backend's digitize function for all backends indices = self.backend.numpy.digitize(inputs, self.bin_boundaries) - return numerical_utils.encode_categorical_inputs( indices, output_mode=self.output_mode, From af901f5f80be62572de78a9f62daa51db0c04207 Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Mon, 28 Jul 2025 09:14:07 -0700 Subject: [PATCH 11/13] fix test --- keras/src/layers/preprocessing/discretization.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index eb3bcca4f556..19dd49078f43 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -96,7 +96,7 @@ def __init__( name=None, ): if dtype is None: - dtype = "int64" if output_mode == "int" else backend.floatx() + dtype = "float32" super().__init__(name=name, dtype=dtype) if sparse and not backend.SUPPORTS_SPARSE_TENSORS: @@ -232,11 +232,14 @@ def call(self, inputs): ) indices = self.backend.numpy.digitize(inputs, self.bin_boundaries) + output_dtype = ( + "int64" if self.output_mode == "int" else self.compute_dtype + ) return numerical_utils.encode_categorical_inputs( indices, output_mode=self.output_mode, depth=len(self.bin_boundaries) + 1, - dtype=self.compute_dtype, + dtype=output_dtype, sparse=self.sparse, backend_module=self.backend, ) From c7b244a7d7576cf866999ffc5bf37ed48e01d654 Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Mon, 4 Aug 2025 00:26:38 -0700 Subject: [PATCH 12/13] fix test --- .../layers/preprocessing/discretization.py | 11 ++- .../preprocessing/discretization_test.py | 75 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index 19dd49078f43..d5c37922c032 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -95,8 +95,6 @@ def __init__( dtype=None, name=None, ): - if dtype is None: - dtype = "float32" super().__init__(name=name, dtype=dtype) if sparse and not backend.SUPPORTS_SPARSE_TENSORS: @@ -154,6 +152,15 @@ def __init__( def input_dtype(self): return backend.floatx() + @property + def compute_dtype(self): + # For "int" output mode, compute in float to avoid input truncation + # but output int64. For other modes, use the default float dtype. + if self.output_mode == "int": + return "float32" + else: + return backend.floatx() + def adapt(self, data, steps=None): """Computes bin boundaries from quantiles in a input dataset. diff --git a/keras/src/layers/preprocessing/discretization_test.py b/keras/src/layers/preprocessing/discretization_test.py index 500c6e9ca039..6323d0e5f839 100644 --- a/keras/src/layers/preprocessing/discretization_test.py +++ b/keras/src/layers/preprocessing/discretization_test.py @@ -205,3 +205,78 @@ def test_call_before_adapt_raises(self): layer = layers.Discretization(num_bins=3) with self.assertRaisesRegex(ValueError, "You need .* call .*adapt"): layer([[0.1, 0.8, 0.9]]) + + def test_model_call_vs_predict_consistency(self): + """Test that model(input) and model.predict(input) produce consistent outputs.""" # noqa: E501 + # Test with int output mode + layer = layers.Discretization( + bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], + output_mode="int", + ) + x = np.array([[0.0, 0.15, 0.21, 0.3], [0.0, 0.17, 0.451, 7.8]]) + + # Create model + inputs = layers.Input(shape=(4,), dtype="float32") + outputs = layer(inputs) + model = models.Model(inputs=inputs, outputs=outputs) + + # Test both execution modes + model_call_output = model(x) + predict_output = model.predict(x) + + # Check consistency + self.assertAllClose(model_call_output, predict_output) + + # Test with one_hot output mode + layer_one_hot = layers.Discretization( + bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], + output_mode="one_hot", + ) + + # Create model + inputs = layers.Input(shape=(4,), dtype="float32") + outputs = layer_one_hot(inputs) + model_one_hot = models.Model(inputs=inputs, outputs=outputs) + + # Test both execution modes + model_call_output = model_one_hot(x) + predict_output = model_one_hot.predict(x) + + # Check consistency + self.assertAllClose(model_call_output, predict_output) + + # Test with multi_hot output mode + layer_multi_hot = layers.Discretization( + bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], + output_mode="multi_hot", + ) + + # Create model + inputs = layers.Input(shape=(4,), dtype="float32") + outputs = layer_multi_hot(inputs) + model_multi_hot = models.Model(inputs=inputs, outputs=outputs) + + # Test both execution modes + model_call_output = model_multi_hot(x) + predict_output = model_multi_hot.predict(x) + + # Check consistency + self.assertAllClose(model_call_output, predict_output) + + # Test with count output mode + layer_count = layers.Discretization( + bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], + output_mode="count", + ) + + # Create model + inputs = layers.Input(shape=(4,), dtype="float32") + outputs = layer_count(inputs) + model_count = models.Model(inputs=inputs, outputs=outputs) + + # Test both execution modes + model_call_output = model_count(x) + predict_output = model_count.predict(x) + + # Check consistency + self.assertAllClose(model_call_output, predict_output) From f29997c60911709ddb789f32f66ff4503dc14b3b Mon Sep 17 00:00:00 2001 From: Divyashree Sreepathihalli Date: Mon, 4 Aug 2025 00:26:52 -0700 Subject: [PATCH 13/13] fix test --- .../layers/preprocessing/discretization.py | 4 +- .../preprocessing/discretization_test.py | 54 ------------------- 2 files changed, 1 insertion(+), 57 deletions(-) diff --git a/keras/src/layers/preprocessing/discretization.py b/keras/src/layers/preprocessing/discretization.py index d5c37922c032..3a4d4b8dfb33 100644 --- a/keras/src/layers/preprocessing/discretization.py +++ b/keras/src/layers/preprocessing/discretization.py @@ -154,10 +154,8 @@ def input_dtype(self): @property def compute_dtype(self): - # For "int" output mode, compute in float to avoid input truncation - # but output int64. For other modes, use the default float dtype. if self.output_mode == "int": - return "float32" + return "int64" else: return backend.floatx() diff --git a/keras/src/layers/preprocessing/discretization_test.py b/keras/src/layers/preprocessing/discretization_test.py index 6323d0e5f839..c96b003833a1 100644 --- a/keras/src/layers/preprocessing/discretization_test.py +++ b/keras/src/layers/preprocessing/discretization_test.py @@ -226,57 +226,3 @@ def test_model_call_vs_predict_consistency(self): # Check consistency self.assertAllClose(model_call_output, predict_output) - - # Test with one_hot output mode - layer_one_hot = layers.Discretization( - bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], - output_mode="one_hot", - ) - - # Create model - inputs = layers.Input(shape=(4,), dtype="float32") - outputs = layer_one_hot(inputs) - model_one_hot = models.Model(inputs=inputs, outputs=outputs) - - # Test both execution modes - model_call_output = model_one_hot(x) - predict_output = model_one_hot.predict(x) - - # Check consistency - self.assertAllClose(model_call_output, predict_output) - - # Test with multi_hot output mode - layer_multi_hot = layers.Discretization( - bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], - output_mode="multi_hot", - ) - - # Create model - inputs = layers.Input(shape=(4,), dtype="float32") - outputs = layer_multi_hot(inputs) - model_multi_hot = models.Model(inputs=inputs, outputs=outputs) - - # Test both execution modes - model_call_output = model_multi_hot(x) - predict_output = model_multi_hot.predict(x) - - # Check consistency - self.assertAllClose(model_call_output, predict_output) - - # Test with count output mode - layer_count = layers.Discretization( - bin_boundaries=[-0.5, 0, 0.1, 0.2, 3], - output_mode="count", - ) - - # Create model - inputs = layers.Input(shape=(4,), dtype="float32") - outputs = layer_count(inputs) - model_count = models.Model(inputs=inputs, outputs=outputs) - - # Test both execution modes - model_call_output = model_count(x) - predict_output = model_count.predict(x) - - # Check consistency - self.assertAllClose(model_call_output, predict_output)