Skip to content

Commit b7b6f97

Browse files
authored
Adapt to the multi-backend Keras (#928)
1 parent b406daf commit b7b6f97

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+785
-562
lines changed

.github/workflows/actions.yml

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ permissions:
1212

1313
jobs:
1414
build:
15-
name: Run tests
15+
name: Test the code with tf.keras
1616
runs-on: ubuntu-latest
1717
steps:
1818
- uses: actions/checkout@v3
@@ -33,20 +33,25 @@ jobs:
3333
- name: Install dependencies
3434
run: |
3535
pip install -e ".[tensorflow-cpu,tests]" --progress-bar off --upgrade
36+
pip install jax[cpu]
3637
- name: Test with pytest
3738
run: |
3839
pytest --cov=keras_tuner --cov-report xml:coverage.xml
3940
- name: Codecov
4041
uses: codecov/codecov-action@v3
41-
compatibility:
42-
name: Check compatibility with TF 2.0
42+
multibackend:
43+
name: Test the code with Keras Core
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
backend: [tensorflow, jax, torch]
4348
runs-on: ubuntu-latest
4449
steps:
4550
- uses: actions/checkout@v3
46-
- name: Set up Python 3.7
51+
- name: Set up Python 3.10
4752
uses: actions/setup-python@v4
4853
with:
49-
python-version: '3.7'
54+
python-version: '3.10'
5055
- name: Get pip cache dir
5156
id: pip-cache
5257
run: |
@@ -59,12 +64,15 @@ jobs:
5964
key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
6065
- name: Install dependencies
6166
run: |
62-
pip install protobuf==3.20.1
63-
pip install "tensorflow<2.1"
67+
pip install -e ".[tensorflow-cpu,tests]" --progress-bar off --upgrade
68+
pip install torch>=2.0.1+cpu --progress-bar off
69+
pip install jax[cpu] --progress-bar off
6470
pip install -e ".[tensorflow-cpu,tests]" --progress-bar off --upgrade
6571
- name: Test with pytest
72+
env:
73+
KERAS_BACKEND: ${{ matrix.backend }}
6674
run: |
67-
pytest keras_tuner/integration_tests/legacy_import_test.py
75+
pytest
6876
format:
6977
name: Check the code format
7078
runs-on: ubuntu-latest
@@ -115,7 +123,7 @@ jobs:
115123
- name: Run the guides
116124
run: bash shell/run_guides.sh
117125
deploy:
118-
needs: [build, format,compatibility]
126+
needs: [build, format,multibackend]
119127
if: github.event_name == 'release' && github.event.action == 'created'
120128
runs-on: ubuntu-latest
121129
steps:
@@ -127,10 +135,10 @@ jobs:
127135
- name: Install dependencies
128136
run: |
129137
python -m pip install --upgrade pip setuptools wheel twine
130-
- name: Build and publish
131-
env:
132-
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
133-
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
134-
run: |
135-
python setup.py sdist bdist_wheel
136-
twine upload dist/*
138+
- name: Build a binary wheel and a source tarball
139+
run: >-
140+
python pip_build.py
141+
- name: Publish distribution to PyPI
142+
uses: pypa/gh-action-pypi-publish@release/v1
143+
with:
144+
password: ${{ secrets.PYPI_PASSWORD }}

.github/workflows/nightly.yml

Lines changed: 0 additions & 41 deletions
This file was deleted.

build.py

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import argparse
12
import glob
23
import os
34
import shutil
@@ -6,38 +7,80 @@
67

78
package = "keras_tuner"
89
build_directory = "build"
9-
if os.path.exists(build_directory):
10-
raise ValueError(f"Directory already exists: {build_directory}")
11-
12-
# Copy sources (`keras_tuner/` directory and setup files) to build directory
13-
os.mkdir(build_directory)
14-
shutil.copytree(package, os.path.join(build_directory, package))
15-
shutil.copy("setup.py", os.path.join(f"{build_directory}", "setup.py"))
16-
shutil.copy("setup.cfg", os.path.join(f"{build_directory}", "setup.cfg"))
17-
os.chdir(build_directory)
18-
19-
# Restructure the codebase so that source files live in `keras_tuner/src`
20-
namex.convert_codebase(package, code_directory="src")
21-
# Generate API __init__.py files in `keras_tuner/`
22-
namex.generate_api_files(package, code_directory="src", verbose=True)
23-
24-
# Make sure to export the __version__ string
25-
from keras_tuner.src import __version__ # noqa: E402
26-
27-
with open(os.path.join(package, "__init__.py")) as f:
28-
init_contents = f.read()
29-
with open(os.path.join(package, "__init__.py"), "w") as f:
30-
f.write(init_contents + "\n\n" + f'__version__ = "{__version__}"\n')
31-
32-
# Build the package
33-
os.system("python3 -m build")
34-
35-
# Save the dist files generated by the build process
36-
os.chdir("..")
37-
if not os.path.exists("dist"):
38-
os.mkdir("dist")
39-
for filename in glob.glob(os.path.join(build_directory, "dist", "*.*")):
40-
shutil.copy(filename, "dist")
41-
42-
# Clean up: remove the build directory (no longer needed)
43-
shutil.rmtree(build_directory)
10+
dist_directory = "dist"
11+
to_copy = [
12+
"setup.py",
13+
"setup.cfg",
14+
"README.md",
15+
]
16+
17+
18+
def build():
19+
if os.path.exists(build_directory):
20+
raise ValueError(f"Directory already exists: {build_directory}")
21+
22+
whl_path = None
23+
try:
24+
# Copy sources (`keras_tuner/` directory and setup files) to build
25+
# directory
26+
os.mkdir(build_directory)
27+
shutil.copytree(package, os.path.join(build_directory, package))
28+
for fname in to_copy:
29+
shutil.copy(fname, os.path.join(f"{build_directory}", fname))
30+
os.chdir(build_directory)
31+
32+
# Restructure the codebase so that source files live in
33+
# `keras_tuner/src`
34+
namex.convert_codebase(package, code_directory="src")
35+
36+
# Generate API __init__.py files in `keras_tuner/`
37+
namex.generate_api_files(package, code_directory="src", verbose=True)
38+
39+
# Make sure to export the __version__ string
40+
from keras_tuner.src import __version__ # noqa: E402
41+
42+
with open(os.path.join(package, "__init__.py")) as f:
43+
init_contents = f.read()
44+
with open(os.path.join(package, "__init__.py"), "w") as f:
45+
f.write(init_contents + "\n\n" + f'__version__ = "{__version__}"\n')
46+
47+
# Build the package
48+
os.system("python3 -m build")
49+
50+
# Save the dist files generated by the build process
51+
os.chdir("..")
52+
if not os.path.exists(dist_directory):
53+
os.mkdir(dist_directory)
54+
for filename in glob.glob(
55+
os.path.join(build_directory, dist_directory, "*.*")
56+
):
57+
shutil.copy(filename, dist_directory)
58+
59+
# Find the .whl file path
60+
for fname in os.listdir(dist_directory):
61+
if __version__ in fname and fname.endswith(".whl"):
62+
whl_path = os.path.abspath(os.path.join(dist_directory, fname))
63+
print(f"Build successful. Wheel file available at {whl_path}")
64+
65+
finally:
66+
# Clean up: remove the build directory (no longer needed)
67+
shutil.rmtree(build_directory)
68+
return whl_path
69+
70+
71+
def install_whl(whl_fpath):
72+
print("Installing wheel file.")
73+
os.system(f"pip3 install {whl_fpath} --force-reinstall --no-dependencies")
74+
75+
76+
if __name__ == "__main__":
77+
parser = argparse.ArgumentParser()
78+
parser.add_argument(
79+
"--install",
80+
action="store_true",
81+
help="Whether to install the generated wheel file.",
82+
)
83+
args = parser.parse_args()
84+
whl_path = build()
85+
if whl_path and args.install:
86+
install_whl(whl_path)

keras_tuner/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,4 @@
2929
from keras_tuner.tuners import Hyperband
3030
from keras_tuner.tuners import RandomSearch
3131
from keras_tuner.tuners import SklearnTuner
32-
from keras_tuner.utils import check_tf_version
33-
34-
check_tf_version()
35-
36-
__version__ = "1.3.5"
32+
from keras_tuner.version import __version__

keras_tuner/applications/augment.py

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,57 @@
1313
# limitations under the License.
1414

1515

16-
import tensorflow as tf
17-
from tensorflow import keras
18-
from tensorflow.keras import layers
19-
2016
from keras_tuner.api_export import keras_tuner_export
21-
22-
try:
23-
from tensorflow.keras.layers.experimental import ( # isort:skip
24-
preprocessing,
25-
) # pytype: disable=import-error
26-
except ImportError: # pragma: no cover
27-
preprocessing = None # pragma: no cover
28-
17+
from keras_tuner.backend import keras
18+
from keras_tuner.backend import ops
19+
from keras_tuner.backend import random
20+
from keras_tuner.backend.keras import layers
2921
from keras_tuner.engine import hypermodel
3022

3123
# dict of functions that create layers for transforms.
3224
# Each function takes a factor (0 to 1) for the strength
3325
# of the transform.
34-
if preprocessing is not None:
35-
TRANSFORMS = {
36-
"translate_x": lambda x: preprocessing.RandomTranslation(x, 0),
37-
"translate_y": lambda y: preprocessing.RandomTranslation(0, y),
38-
"rotate": preprocessing.RandomRotation,
39-
"contrast": preprocessing.RandomContrast,
40-
}
26+
TRANSFORMS = {
27+
"translate_x": lambda x: layers.RandomTranslation(x, 0),
28+
"translate_y": lambda y: layers.RandomTranslation(0, y),
29+
"rotate": layers.RandomRotation,
30+
"contrast": layers.RandomContrast,
31+
}
32+
33+
34+
@keras.saving.register_keras_serializable(package="keras_tuner")
35+
class RandAugment(keras.layers.Layer):
36+
"""A single RandAugment layer."""
37+
38+
def __init__(self, layers, factors):
39+
super().__init__()
40+
self.layers = layers
41+
self.factors = factors
42+
43+
def call(self, inputs):
44+
x = inputs
45+
batch_size = ops.shape(x)[0]
46+
# selection tensor determines operation for each sample.
47+
selection = ops.cast(
48+
random.uniform((batch_size, 1, 1, 1), maxval=len(self.layers)),
49+
dtype="int32",
50+
)
51+
52+
for i, layer in enumerate(self.layers):
53+
factor = self.factors[i]
54+
if factor == 0:
55+
continue
56+
transform_layer = TRANSFORMS[layer](factor)
57+
x_trans = transform_layer(x)
58+
59+
# For each sample, apply the transform if and only if
60+
# selection matches the transform index `i`
61+
x = ops.where(ops.equal(i, selection), x_trans, x)
62+
63+
return x
64+
65+
def compute_output_shape(self, input_shape):
66+
return input_shape
4167

4268

4369
@keras_tuner_export("keras_tuner.applications.HyperImageAugment")
@@ -137,12 +163,6 @@ def __init__(
137163
augment_layers=3,
138164
**kwargs,
139165
):
140-
if preprocessing is None:
141-
raise ImportError(
142-
"HyperImageAugment requires tensorflow>=2.3.0, "
143-
f"but the current version is {tf.__version__}."
144-
)
145-
146166
if input_shape is None and input_tensor is None:
147167
raise ValueError(
148168
"You must specify either `input_shape` or `input_tensor`."
@@ -208,27 +228,14 @@ def _build_randaug_layers(self, inputs, hp):
208228
)
209229
x = inputs
210230
for _ in range(augment_layers):
211-
# selection tensor determines operation for each sample.
212-
batch_size = tf.shape(x)[0]
213-
selection = tf.random.uniform(
214-
[batch_size, 1, 1, 1],
215-
maxval=len(self.transforms),
216-
dtype="int32",
217-
)
218-
231+
factors = []
219232
for i, (transform, (f_min, f_max)) in enumerate(self.transforms):
220233
# Factor for each transform is determined per each trial.
221234
factor = hp.Float(
222235
f"factor_{transform}", f_min, f_max, default=f_min
223236
)
224-
if factor == 0:
225-
continue
226-
transform_layer = TRANSFORMS[transform](factor)
227-
x_trans = transform_layer(x)
228-
229-
# For each sample, apply the transform if and only if
230-
# selection matches the transform index `i`
231-
x = tf.where(tf.equal(i, selection), x_trans, x)
237+
factors.append(factor)
238+
x = RandAugment([layer for layer, _ in self.transforms], factors)(x)
232239
return x
233240

234241
def _build_fixedaug_layers(self, inputs, hp):

0 commit comments

Comments
 (0)