Repository: keras-team/keras

Issue:
GlobalMax{Avg}Pooling output infinity or NaN when the input shape is 0

**System information**.
- Have I written custom code (as opposed to using a stock example script provided in Keras): Yes
- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): Ubuntu 20.04
- TensorFlow installed from (source or binary): binary
- TensorFlow version (use command below): latest
- Python version: 2.8.0
- Bazel version (if compiling from source): N/A
- GPU model and memory: N/A
- Exact command to reproduce:

```
import keras
from keras.layers import *
x = keras.layers.Input((5, 0, 16, 16))
layer1 = keras.layers.GlobalMaxPooling3D()
layer2 = keras.layers.GlobalAveragePooling3D()
y1 = layer1(x)
y2 = layer2(x)
model1 = keras.models.Model(x,y1)
model2 = keras.models.Model(x,y2)
import numpy as np
input = np.random.rand(10, 5, 0, 16, 16)
res1 = model1.predict(input)
res2 = model2.predict(input)
print(res1, res2)
```

**Describe the problem**.
The behavior of GlobalMax{Average}PoolingND is undefined when the feature dimension of input is zero. I compare the result with another library ONNXRuntime, it will directly raise an exception as follows:
```
Status Message: /onnxruntime_src/onnxruntime/core/providers/cpu/nn/pool_attributes.h:101 std::vector<long int> onnxruntime::PoolAttributes::SetOutputSize(const onnxruntime::TensorShape&, int64_t, std::vector<long int>*) const input_shape.Size() > 0 || input_shape[0] == 0 was false. Invalid input shape. Only N can be zero. Got:{100,16,5,0,5}
```


**Describe the current behavior**.
TensorFlow will either output nan or infinity when the feature dimension of tensor is zero

**Describe the expected behavior**.
I guess an exception would be better. The tensor with empty shape should be exposed instead of outputting nan or inf.

**[Contributing](https://github.com/keras-team/keras/blob/master/CONTRIBUTING.md)**.

- Do you want to contribute a PR? (yes/no): no
- If yes, please read [this page](https://github.com/keras-team/keras/blob/master/CONTRIBUTING.md) for instructions
- Briefly describe your candidate solution(if contributing):

**Standalone code to reproduce the issue**.
Please refer to the above code for reproduction.



Discussion:

Resolving patch:
diff --git a/keras/layers/pooling/base_global_pooling1d.py b/keras/layers/pooling/base_global_pooling1d.py
--- a/keras/layers/pooling/base_global_pooling1d.py
+++ b/keras/layers/pooling/base_global_pooling1d.py
@@ -31,6 +31,21 @@ def __init__(self, data_format="channels_last", keepdims=False, **kwargs):
         self.data_format = conv_utils.normalize_data_format(data_format)
         self.keepdims = keepdims
 
+    def _validate_reduction_axis(self, input_shape, axes):
+        for axis in axes:
+            if input_shape[axis] == 0:
+                raise ValueError(
+                    f"Incorrect input shape {input_shape} "
+                    f"with dimension 0 at reduction axis {axis}."
+                )
+
+    def build(self, input_shape):
+        input_shape = tf.TensorShape(input_shape).as_list()
+        if self.data_format == "channels_last":
+            self._validate_reduction_axis(input_shape, [1])
+        else:
+            self._validate_reduction_axis(input_shape, [2])
+
     def compute_output_shape(self, input_shape):
         input_shape = tf.TensorShape(input_shape).as_list()
         if self.data_format == "channels_first":
diff --git a/keras/layers/pooling/base_global_pooling2d.py b/keras/layers/pooling/base_global_pooling2d.py
--- a/keras/layers/pooling/base_global_pooling2d.py
+++ b/keras/layers/pooling/base_global_pooling2d.py
@@ -31,6 +31,21 @@ def __init__(self, data_format=None, keepdims=False, **kwargs):
         self.input_spec = InputSpec(ndim=4)
         self.keepdims = keepdims
 
+    def _validate_reduction_axis(self, input_shape, axes):
+        for axis in axes:
+            if input_shape[axis] == 0:
+                raise ValueError(
+                    f"Incorrect input shape {input_shape} "
+                    f"with dimension 0 at reduction axis {axis}."
+                )
+
+    def build(self, input_shape):
+        input_shape = tf.TensorShape(input_shape).as_list()
+        if self.data_format == "channels_last":
+            self._validate_reduction_axis(input_shape, [1, 2])
+        else:
+            self._validate_reduction_axis(input_shape, [2, 3])
+
     def compute_output_shape(self, input_shape):
         input_shape = tf.TensorShape(input_shape).as_list()
         if self.data_format == "channels_last":
diff --git a/keras/layers/pooling/base_global_pooling3d.py b/keras/layers/pooling/base_global_pooling3d.py
--- a/keras/layers/pooling/base_global_pooling3d.py
+++ b/keras/layers/pooling/base_global_pooling3d.py
@@ -31,6 +31,21 @@ def __init__(self, data_format=None, keepdims=False, **kwargs):
         self.input_spec = InputSpec(ndim=5)
         self.keepdims = keepdims
 
+    def _validate_reduction_axis(self, input_shape, axes):
+        for axis in axes:
+            if input_shape[axis] == 0:
+                raise ValueError(
+                    f"Incorrect input shape {input_shape} "
+                    f"with dimension 0 at reduction axis {axis}."
+                )
+
+    def build(self, input_shape):
+        input_shape = tf.TensorShape(input_shape).as_list()
+        if self.data_format == "channels_last":
+            self._validate_reduction_axis(input_shape, [1, 2, 3])
+        else:
+            self._validate_reduction_axis(input_shape, [2, 3, 4])
+
     def compute_output_shape(self, input_shape):
         input_shape = tf.TensorShape(input_shape).as_list()
         if self.data_format == "channels_last":


Test patch:
diff --git a/keras/layers/pooling/global_average_pooling_test.py b/keras/layers/pooling/global_average_pooling_test.py
--- a/keras/layers/pooling/global_average_pooling_test.py
+++ b/keras/layers/pooling/global_average_pooling_test.py
@@ -155,6 +155,16 @@ def test_global_average_pooling_1d_keepdims_masking_support(self):
         self.assertAllEqual((2, 1, 4), output.shape)
         self.assertAllClose(output[0, 0], model_input[0, 0, :])
 
+    def test_global_average_pooling_1d_invalid_input_dimension(self):
+        with self.assertRaisesRegex(ValueError, r"""Incorrect input shape"""):
+            layer = keras.layers.GlobalAveragePooling1D()
+            layer.build((None, 0, 2))
+
+    def test_global_average_pooling_3d_invalid_input_dimension(self):
+        with self.assertRaisesRegex(ValueError, r"""Incorrect input shape"""):
+            layer = keras.layers.GlobalAveragePooling3D(keepdims=True)
+            layer.build((None, 0, 16, 16, 3))
+
 
 if __name__ == "__main__":
     tf.test.main()
diff --git a/keras/layers/pooling/global_max_pooling_test.py b/keras/layers/pooling/global_max_pooling_test.py
--- a/keras/layers/pooling/global_max_pooling_test.py
+++ b/keras/layers/pooling/global_max_pooling_test.py
@@ -122,6 +122,16 @@ def test_global_max_pooling_3d_keepdims(self):
             expected_output_shape=(None, 1, 1, 1, 3),
         )
 
+    def test_global_max_pooling_1d_invalid_input_dimension(self):
+        with self.assertRaisesRegex(ValueError, r"""Incorrect input shape"""):
+            layer = keras.layers.GlobalMaxPooling1D()
+            layer.build((None, 0, 2))
+
+    def test_global_max_pooling_3d_invalid_input_dimension(self):
+        with self.assertRaisesRegex(ValueError, r"""Incorrect input shape"""):
+            layer = keras.layers.GlobalMaxPooling3D(keepdims=True)
+            layer.build((None, 0, 16, 16, 3))
+
 
 if __name__ == "__main__":
     tf.test.main()