Repository: keras-team/keras-tuner

Issue:
Support TF 2.0
enclose the unsupported imports with try clauses.
Refer to:
https://github.com/keras-team/keras-tuner/pull/457/files#diff-987f464c76e4c860bedae74b852a2ff0e3c89960b87d551b655d3a0f356f1be5R20-R28

Discussion:

Patch:
diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml
index d5df3e9..c3023f8 100644
--- a/.github/workflows/actions.yml
+++ b/.github/workflows/actions.yml
@@ -12,6 +12,7 @@ jobs:
     strategy:
       matrix:
         os: [windows-latest, ubuntu-18.04]
+        tf: [tensorflow==2.0.0, tensorflow]
     steps:
       - uses: actions/checkout@v2
       - name: Set up Python 3.6
@@ -32,8 +33,7 @@ jobs:
             ${{ runner.os }}-pip-
       - name: Install dependencies
         run: |
-          pip install tensorflow keras_autodoc
-          pip install Sphinx==3.0.4
+          pip install ${{ matrix.tf }}
           pip install -e ".[tests]" --progress-bar off --upgrade
       - name: Test with pytest
         run: |
@@ -48,10 +48,10 @@ jobs:
     runs-on: ubuntu-18.04
     steps:
       - uses: actions/checkout@v2
-      - name: Set up Python 3.6
+      - name: Set up Python 3.7
         uses: actions/setup-python@v1
         with:
-          python-version: 3.6
+          python-version: 3.7
       - name: Get pip cache dir
         id: pip-cache
         run: |
@@ -66,8 +66,7 @@ jobs:
             ${{ runner.os }}-pip-
       - name: Install dependencies
         run: |
-          pip install tensorflow keras_autodoc
-          pip install Sphinx==3.0.4
+          pip install tensorflow
           pip install -e ".[tests]" --progress-bar off --upgrade
       - name: Lint
         run: bash shell/lint.sh
diff --git a/keras_tuner/applications/__init__.py b/keras_tuner/applications/__init__.py
index af5cb9c..4cfe587 100644
--- a/keras_tuner/applications/__init__.py
+++ b/keras_tuner/applications/__init__.py
@@ -15,5 +15,6 @@
 from __future__ import absolute_import
 
 from .augment import HyperImageAugment
+from .efficientnet import HyperEfficientNet
 from .resnet import HyperResNet
 from .xception import HyperXception
diff --git a/keras_tuner/applications/augment.py b/keras_tuner/applications/augment.py
index 3f426f9..037017d 100644
--- a/keras_tuner/applications/augment.py
+++ b/keras_tuner/applications/augment.py
@@ -20,19 +20,26 @@ from __future__ import print_function
 import tensorflow as tf
 import tensorflow.keras as keras
 from tensorflow.keras import layers
-from tensorflow.keras.layers.experimental import preprocessing
+
+try:
+    from tensorflow.keras.layers.experimental import (  # isort:skip
+        preprocessing,
+    )  # pytype: disable=import-error
+except ImportError:
+    preprocessing = None
 
 from keras_tuner.engine import hypermodel
 
 # dict of functions that create layers for transforms.
 # Each function takes a factor (0 to 1) for the strength
 # of the transform.
-TRANSFORMS = {
-    "translate_x": lambda x: preprocessing.RandomTranslation(x, 0),
-    "translate_y": lambda y: preprocessing.RandomTranslation(0, y),
-    "rotate": preprocessing.RandomRotation,
-    "contrast": preprocessing.RandomContrast,
-}
+if preprocessing is not None:
+    TRANSFORMS = {
+        "translate_x": lambda x: preprocessing.RandomTranslation(x, 0),
+        "translate_y": lambda y: preprocessing.RandomTranslation(0, y),
+        "rotate": preprocessing.RandomRotation,
+        "contrast": preprocessing.RandomContrast,
+    }
 
 
 class HyperImageAugment(hypermodel.HyperModel):
@@ -131,6 +138,11 @@ class HyperImageAugment(hypermodel.HyperModel):
         augment_layers=3,
         **kwargs,
     ):
+        if preprocessing is None:
+            raise ImportError(
+                "HyperImageAugment requires tensorflow>=2.3.0, "
+                f"but the current version is {tf.__version__}."
+            )
 
         if input_shape is None and input_tensor is None:
             raise ValueError(
diff --git a/keras_tuner/applications/efficientnet.py b/keras_tuner/applications/efficientnet.py
index a7af079..7b491bc 100644
--- a/keras_tuner/applications/efficientnet.py
+++ b/keras_tuner/applications/efficientnet.py
@@ -17,21 +17,34 @@
 import tensorflow as tf
 import tensorflow.keras as keras
 from tensorflow.keras import layers
-from tensorflow.keras.applications import efficientnet
-from tensorflow.keras.layers.experimental import preprocessing
 
 from keras_tuner.engine import hypermodel
 
-EFFICIENTNET_MODELS = {
-    "B0": efficientnet.EfficientNetB0,
-    "B1": efficientnet.EfficientNetB1,
-    "B2": efficientnet.EfficientNetB2,
-    "B3": efficientnet.EfficientNetB3,
-    "B4": efficientnet.EfficientNetB4,
-    "B5": efficientnet.EfficientNetB5,
-    "B6": efficientnet.EfficientNetB6,
-    "B7": efficientnet.EfficientNetB7,
-}
+try:
+    from tensorflow.keras.applications import (  # isort:skip
+        efficientnet,
+    )  # pytype: disable=import-error
+except ImportError:
+    efficientnet = None
+
+try:
+    from tensorflow.keras.layers.experimental import (  # isort:skip
+        preprocessing,
+    )  # pytype: disable=import-error
+except ImportError:
+    preprocessing = None
+
+if efficientnet is not None:
+    EFFICIENTNET_MODELS = {
+        "B0": efficientnet.EfficientNetB0,
+        "B1": efficientnet.EfficientNetB1,
+        "B2": efficientnet.EfficientNetB2,
+        "B3": efficientnet.EfficientNetB3,
+        "B4": efficientnet.EfficientNetB4,
+        "B5": efficientnet.EfficientNetB5,
+        "B6": efficientnet.EfficientNetB6,
+        "B7": efficientnet.EfficientNetB7,
+    }
 
 EFFICIENTNET_IMG_SIZE = {
     "B0": 224,
@@ -73,8 +86,13 @@ class HyperEfficientNet(hypermodel.HyperModel):
         input_tensor=None,
         classes=None,
         augmentation_model=None,
-        **kwargs
+        **kwargs,
     ):
+        if preprocessing is None:
+            raise ImportError(
+                "HyperEfficientNet requires tensorflow>=2.3.0, "
+                f"but the current version is {tf.__version__}."
+            )
         if not isinstance(
             augmentation_model, (hypermodel.HyperModel, keras.Model, type(None))
         ):
diff --git a/keras_tuner/engine/tuner.py b/keras_tuner/engine/tuner.py
index 3b4b67b..b9206f4 100644
--- a/keras_tuner/engine/tuner.py
+++ b/keras_tuner/engine/tuner.py
@@ -112,7 +112,9 @@ class Tuner(base_tuner.BaseTuner):
 
         # Support multi-worker distribution strategies w/ distributed tuning.
         # Only the chief worker in each cluster should report results.
-        if self.distribution_strategy is not None:
+        if self.distribution_strategy is not None and hasattr(
+            self.distribution_strategy.extended, "_in_multi_worker_mode"
+        ):
             self.oracle.multi_worker = (
                 self.distribution_strategy.extended._in_multi_worker_mode()
             )
@@ -323,9 +325,11 @@ class Tuner(base_tuner.BaseTuner):
             self._get_checkpoint_dir(trial_id, epoch),
             "checkpoint",
         )
-        if isinstance(
-            self.distribution_strategy, tf.distribute.TPUStrategy
-        ) and not self.project_dir.startswith("gs://"):
+        if (
+            hasattr(tf.distribute, "TPUStrategy")
+            and isinstance(self.distribution_strategy, tf.distribute.TPUStrategy)
+            and not self.project_dir.startswith("gs://")
+        ):
             # TPU strategy only support saving h5 format on local path
             return checkpoint_fname + ".h5"
         return checkpoint_fname


Test patch:
diff --git a/tests/keras_tuner/applications/augment_test.py b/tests/keras_tuner/applications/augment_test.py
index 2e3b831..144e05c 100644
--- a/tests/keras_tuner/applications/augment_test.py
+++ b/tests/keras_tuner/applications/augment_test.py
@@ -17,6 +17,8 @@ import os
 
 import numpy as np
 import pytest
+import tensorflow as tf
+from packaging.version import parse
 from tensorflow import keras
 
 from keras_tuner.applications import augment as aug_module
@@ -24,6 +26,10 @@ from keras_tuner.engine import hyperparameters as hp_module
 
 
 @pytest.mark.skipif("TRAVIS" in os.environ, reason="Causes CI to stall")
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_transforms_search_space():
     hm = aug_module.HyperImageAugment(input_shape=(32, 32, 3))
     # Default choice
@@ -44,6 +50,20 @@ def test_transforms_search_space():
     ]
 
 
+def test_tf_version_too_low_error():
+    pp_module = aug_module.preprocessing
+    aug_module.preprocessing = None
+
+    with pytest.raises(ImportError, match="HyperImageAugment requires"):
+        aug_module.HyperImageAugment()
+
+    aug_module.preprocessing = pp_module
+
+
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_input_requirement():
     hp = hp_module.HyperParameters()
     with pytest.raises(ValueError, match=r".*must specify.*"):
@@ -58,6 +78,10 @@ def test_input_requirement():
     assert model.built
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_model_construction_factor_zero():
     hp = hp_module.HyperParameters()
     hm = aug_module.HyperImageAugment(input_shape=(None, None, 3))
@@ -72,6 +96,10 @@ def test_model_construction_factor_zero():
     assert len(model.layers) == 1
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_model_construction_fixed_aug():
     hp = hp_module.HyperParameters()
     hm = aug_module.HyperImageAugment(
@@ -89,6 +117,10 @@ def test_model_construction_fixed_aug():
     assert (out != 1).sum() == 0
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_model_construction_rand_aug():
     hp = hp_module.HyperParameters()
     hm = aug_module.HyperImageAugment(input_shape=(None, None, 3), rotate=[0.2, 0.5])
@@ -104,6 +136,10 @@ def test_model_construction_rand_aug():
     assert (out != 1).sum() == 0
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_hyperparameter_selection_and_hp_defaults_fixed_aug():
     hp = hp_module.HyperParameters()
     hm = aug_module.HyperImageAugment(
@@ -120,6 +156,10 @@ def test_hyperparameter_selection_and_hp_defaults_fixed_aug():
     assert "factor_contrast" not in hp.values
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_hyperparameter_existence_and_hp_defaults_rand_aug():
     hp = hp_module.HyperParameters()
     hm = aug_module.HyperImageAugment(
@@ -129,6 +169,10 @@ def test_hyperparameter_existence_and_hp_defaults_rand_aug():
     assert hp.get("augment_layers") == 2
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_hyperparameter_override_fixed_aug():
     hp = hp_module.HyperParameters()
     hp.Fixed("factor_rotate", 0.9)
@@ -141,6 +185,10 @@ def test_hyperparameter_override_fixed_aug():
     assert hp.get("factor_contrast") == 0.0
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers only exist in TF2.3+.",
+)
 def test_hyperparameter_override_rand_aug():
     hp = hp_module.HyperParameters()
     hp.Fixed("randaug_mag", 1.0)
diff --git a/tests/keras_tuner/applications/efficientnet_test.py b/tests/keras_tuner/applications/efficientnet_test.py
index 48d87af..3db65d5 100644
--- a/tests/keras_tuner/applications/efficientnet_test.py
+++ b/tests/keras_tuner/applications/efficientnet_test.py
@@ -16,12 +16,18 @@
 import numpy as np
 import pytest
 import tensorflow as tf
+from packaging.version import parse
 
 from keras_tuner.applications import efficientnet
 from keras_tuner.engine import hypermodel as hm_module
 from keras_tuner.engine import hyperparameters as hp_module
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 @pytest.mark.parametrize("version", ["B0", "B1"])
 def test_model_construction(version):
     hp = hp_module.HyperParameters()
@@ -37,6 +43,21 @@ def test_model_construction(version):
     assert out.shape == (1, 10)
 
 
+def test_tf_version_too_low_error():
+    pp_module = efficientnet.preprocessing
+    efficientnet.preprocessing = None
+
+    with pytest.raises(ImportError, match="HyperEfficientNet requires"):
+        efficientnet.HyperEfficientNet()
+
+    efficientnet.preprocessing = pp_module
+
+
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 def test_hyperparameter_existence_and_defaults():
     hp = hp_module.HyperParameters()
     hypermodel = efficientnet.HyperEfficientNet(
@@ -49,6 +70,11 @@ def test_hyperparameter_existence_and_defaults():
     assert hp.get("pooling") == "avg"
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 def test_hyperparameter_override():
     hp = hp_module.HyperParameters()
     hp.Choice("version", ["B1"])
@@ -61,6 +87,11 @@ def test_hyperparameter_override():
     assert hp.get("top_dropout_rate") == 0.5
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 def test_input_tensor():
     hp = hp_module.HyperParameters()
     inputs = tf.keras.Input(shape=(256, 256, 3))
@@ -69,6 +100,11 @@ def test_input_tensor():
     assert model.inputs == [inputs]
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 def test_override_compiling_phase():
     class MyHyperEfficientNet(efficientnet.HyperEfficientNet):
         def _compile(self, model, hp):
@@ -93,6 +129,11 @@ def test_override_compiling_phase():
     assert hp.values["optimizer"] == "adam"
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 def test_augmentation_param_invalid_input():
     with pytest.raises(ValueError):
         efficientnet.HyperEfficientNet(
@@ -100,6 +141,11 @@ def test_augmentation_param_invalid_input():
         )
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 def test_augmentation_param_fixed_model():
     hp = hp_module.HyperParameters()
     aug_model = tf.keras.Sequential(name="aug")
@@ -110,6 +156,11 @@ def test_augmentation_param_fixed_model():
     assert model.layers[1].name == "aug"
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="Preprocessing layers and "
+    "applications.efficientnet only exist in TF2.3+.",
+)
 def test_augmentation_param_hyper_model():
     class HyperAug(hm_module.HyperModel):
         def build(self, hp):
diff --git a/tests/keras_tuner/engine/tuner_correctness_test.py b/tests/keras_tuner/engine/tuner_correctness_test.py
index 491b6f9..4c2c47e 100644
--- a/tests/keras_tuner/engine/tuner_correctness_test.py
+++ b/tests/keras_tuner/engine/tuner_correctness_test.py
@@ -17,6 +17,7 @@ from unittest import mock
 import numpy as np
 import pytest
 import tensorflow as tf
+from packaging.version import parse
 from tensorflow import keras
 
 import keras_tuner
@@ -194,6 +195,10 @@ def test_checkpoint_removal(tmp_dir):
     assert not tf.io.gfile.exists(tuner._get_checkpoint_fname(trial_id, 10))
 
 
+@pytest.mark.skipif(
+    parse(tf.__version__) < parse("2.3.0"),
+    reason="TPUStrategy only exists in TF2.3+.",
+)
 def test_checkpoint_fname_tpu(tmp_dir):
     def build_model(hp):
         model = keras.Sequential(