Skip to content

Commit

Permalink
Support tensorflow 2.5 through 2.8. (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
jesnie authored May 24, 2022
1 parent b47ffc7 commit 02268eb
Show file tree
Hide file tree
Showing 18 changed files with 96 additions and 32 deletions.
28 changes: 26 additions & 2 deletions .github/workflows/quality-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,35 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
tensorflow: ["~=2.5.0"]
python-version: ["3.7", "3.8", "3.9", "3.10"]
tensorflow: ["~=2.5.0", "~=2.6.0", "~=2.7.0", "~=2.8.0"]
include:
- tensorflow: "~=2.5.0"
keras: "~=2.6.0"
tensorflow-probability: "~=0.13.0"
- tensorflow: "~=2.6.0"
keras: "~=2.6.0"
tensorflow-probability: "~=0.14.0"
- tensorflow: "~=2.7.0"
keras: "~=2.7.0"
tensorflow-probability: "~=0.15.0"
- tensorflow: "~=2.8.0"
keras: "~=2.8.0"
tensorflow-probability: "~=0.16.0"
exclude:
# These older versions of TensorFlow don't work with Python 3.10:
- python-version: "3.10"
tensorflow: "~=2.5.0"
- python-version: "3.10"
tensorflow: "~=2.6.0"
- python-version: "3.10"
tensorflow: "~=2.7.0"

name: Python-${{ matrix.python-version }} tensorflow${{ matrix.tensorflow }}
env:
VERSION_TF: ${{ matrix.tensorflow }}
VERSION_KERAS: ${{ matrix.keras }}
VERSION_TFP: ${{ matrix.tensorflow-probability }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ install: ## Install repo for developement
-r notebook_requirements.txt \
-r tests_requirements.txt \
tensorflow${VERSION_TF} \
keras${VERSION_KERAS} \
tensorflow-probability${VERSION_TFP} \
-e .

docs: ## Build the documentation
Expand Down Expand Up @@ -85,7 +87,7 @@ test: ## Run unit and integration tests with pytest
--cov-config .coveragerc \
--cov-report term \
--cov-report xml \
--cov-fail-under=97 \
--cov-fail-under=94 \
--junitxml=reports/junit.xml \
-v --tb=short --durations=10 \
$(TESTS_NAME)
Expand Down
2 changes: 1 addition & 1 deletion docs/notebooks/intro.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

# %%
def motorcycle_data():
""" Return inputs and outputs for the motorcycle dataset. We normalise the outputs. """
"""Return inputs and outputs for the motorcycle dataset. We normalise the outputs."""
df = pd.read_csv("./data/motor.csv", index_col=0)
X, Y = df["times"].values.reshape(-1, 1), df["accel"].values.reshape(-1, 1)
Y = (Y - Y.mean()) / Y.std()
Expand Down
3 changes: 2 additions & 1 deletion gpflux/architectures/constant_input_dim_deep_gp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""

from dataclasses import dataclass
from typing import cast

import numpy as np
import tensorflow as tf
Expand Down Expand Up @@ -144,7 +145,7 @@ def build_constant_input_dim_deep_gp(X: np.ndarray, num_layers: int, config: Con
mean_function = construct_mean_function(X_running, D_in, D_out)
X_running = mean_function(X_running)
if tf.is_tensor(X_running):
X_running = X_running.numpy()
X_running = cast(tf.Tensor, X_running).numpy()
q_sqrt_scaling = config.inner_layer_qsqrt_factor

layer = GPLayer(
Expand Down
4 changes: 2 additions & 2 deletions gpflux/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def on_train_batch_end(self, batch: int, logs: Optional[Mapping] = None) -> None
self.monitor(batch)

def on_epoch_end(self, epoch: int, logs: Optional[Mapping] = None) -> None:
""" Write to TensorBoard if :attr:`update_freq` equals ``"epoch"``. """
"""Write to TensorBoard if :attr:`update_freq` equals ``"epoch"``."""
super().on_epoch_end(epoch, logs=logs)

if self.update_freq == "epoch":
Expand All @@ -156,7 +156,7 @@ def _parameter_of_interest(self, match: str) -> bool:
return self._LAYER_PARAMETER_REGEXP.match(match) is not None

def run(self, **unused_kwargs: Any) -> None:
""" Write the model's parameters to TensorBoard. """
"""Write the model's parameters to TensorBoard."""

for name, parameter in parameter_dict(self.model).items():
if not self._parameter_of_interest(name):
Expand Down
3 changes: 2 additions & 1 deletion gpflux/layers/likelihood_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from gpflow.likelihoods import Likelihood

from gpflux.layers.trackable_layer import TrackableLayer
from gpflux.types import unwrap_dist


class LikelihoodLayer(TrackableLayer):
Expand Down Expand Up @@ -75,7 +76,7 @@ def call(
containing mean and variance only.
"""
# TODO: add support for non-distribution inputs? or other distributions?
assert isinstance(inputs, tfp.distributions.MultivariateNormalDiag)
assert isinstance(unwrap_dist(inputs), tfp.distributions.MultivariateNormalDiag)
F_mean = inputs.loc
F_var = inputs.scale.diag ** 2

Expand Down
4 changes: 3 additions & 1 deletion gpflux/losses.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import gpflow
from gpflow.base import TensorType

from gpflux.types import unwrap_dist


class LikelihoodLoss(tf.keras.losses.Loss):
r"""
Expand Down Expand Up @@ -77,7 +79,7 @@ def call(
Note that we deviate from the Keras Loss interface by calling the
second argument *f_prediction* rather than *y_pred*.
"""
if isinstance(f_prediction, tfp.distributions.MultivariateNormalDiag):
if isinstance(unwrap_dist(f_prediction), tfp.distributions.MultivariateNormalDiag):

F_mu = f_prediction.loc
F_var = f_prediction.scale.diag ** 2
Expand Down
2 changes: 1 addition & 1 deletion gpflux/models/deep_gp.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def sample_dgp(model: DeepGP) -> Sample: # TODO: should this be part of a [Vani
# TODO: error check that all layers implement .sample()?

class ChainedSample(Sample):
""" This class chains samples from consecutive layers. """
"""This class chains samples from consecutive layers."""

def __call__(self, X: TensorType) -> tf.Tensor:
for f in function_draws:
Expand Down
8 changes: 4 additions & 4 deletions gpflux/sampling/kernel_with_feature_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def __init__(
self._feature_coefficients = feature_coefficients # [L, 1]

def K(self, X: TensorType, X2: Optional[TensorType] = None) -> tf.Tensor:
""" Approximate the true kernel by an inner product between feature functions. """
"""Approximate the true kernel by an inner product between feature functions."""
phi = self._feature_functions(X) # [N, L]
if X2 is None:
phi2 = phi
Expand All @@ -81,7 +81,7 @@ def K(self, X: TensorType, X2: Optional[TensorType] = None) -> tf.Tensor:
return r

def K_diag(self, X: TensorType) -> tf.Tensor:
""" Approximate the true kernel by an inner product between feature functions. """
"""Approximate the true kernel by an inner product between feature functions."""
phi_squared = self._feature_functions(X) ** 2 # [N, L]
r = tf.reduce_sum(phi_squared * tf.transpose(self._feature_coefficients), axis=1) # [N,]
N = tf.shape(X)[0]
Expand Down Expand Up @@ -162,12 +162,12 @@ def __init__(

@property
def feature_functions(self) -> tf.keras.layers.Layer:
r""" Return the kernel's features :math:`\phi_i(\cdot)`. """
r"""Return the kernel's features :math:`\phi_i(\cdot)`."""
return self._feature_functions

@property
def feature_coefficients(self) -> tf.Tensor:
r""" Return the kernel's coefficients :math:`\lambda_i`. """
r"""Return the kernel's coefficients :math:`\lambda_i`."""
return self._feature_coefficients

def K(self, X: TensorType, X2: Optional[TensorType] = None) -> tf.Tensor:
Expand Down
13 changes: 13 additions & 0 deletions gpflux/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@
from typing import List, Tuple, Union

import tensorflow as tf
import tensorflow_probability as tfp

from gpflow.base import TensorType


def unwrap_dist(dist: tfp.distributions.Distribution) -> tfp.distributions.Distribution:
"""
Unwrap the given distribution, if it is wrapped in a ``_TensorCoercible``.
"""
while True:
inner = getattr(dist, "tensor_distribution", None)
if inner is None:
return dist
dist = inner


ShapeType = Union[tf.TensorShape, List[int], Tuple[int, ...]]
r""" Union of valid types for describing the shape of a `tf.Tensor`\ (-like) object """

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"gpflow>=2.1",
"numpy",
"scipy",
"tensorflow>=2.5.0,<2.6.0",
"tensorflow-probability>=0.12.0,<0.14.0",
"tensorflow>=2.5.0,<2.9.0",
"tensorflow-probability>=0.13.0,<0.17.0",
]

with open("README.md", "r") as file:
Expand Down
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import numpy as np
import pytest
import tensorflow as tf
from packaging.version import Version

from gpflow.kernels import SquaredExponential

# TODO: It would be great to make serialisation work in general. See:
# https://github.com/GPflow/GPflow/issues/1658
skip_serialization_tests = pytest.mark.skipif(
Version(tf.__version__) >= Version("2.6"),
reason="GPflow Parameter cannot be serialized in newer version of TensorFlow.",
)


@pytest.fixture
def test_data():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from gpflux.layers.basis_functions.fourier_features.quadrature import QuadratureFourierFeatures
from gpflux.layers.basis_functions.fourier_features.quadrature.gaussian import QFF_SUPPORTED_KERNELS
from tests.conftest import skip_serialization_tests


@pytest.fixture(name="n_dims", params=[1, 2, 3])
Expand Down Expand Up @@ -145,6 +146,7 @@ def test_fourier_features_shapes(n_components, n_dims, batch_size):
np.testing.assert_equal(features.shape, output_shape)


@skip_serialization_tests
def test_keras_testing_util_layer_test_1D(kernel_cls, batch_size, n_components):
kernel = kernel_cls()

Expand All @@ -163,6 +165,7 @@ def test_keras_testing_util_layer_test_1D(kernel_cls, batch_size, n_components):
)


@skip_serialization_tests
def test_keras_testing_util_layer_test_multidim(kernel_cls, batch_size, n_dims, n_components):
kernel = kernel_cls()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
RandomFourierFeaturesCosine,
)
from gpflux.layers.basis_functions.fourier_features.random.base import RFF_SUPPORTED_KERNELS
from tests.conftest import skip_serialization_tests


@pytest.fixture(name="n_dims", params=[1, 2, 3, 5, 10, 20])
Expand Down Expand Up @@ -162,6 +163,7 @@ def test_fourier_features_shapes(basis_func_cls, n_components, n_dims, batch_siz
np.testing.assert_equal(features.shape, output_shape)


@skip_serialization_tests
def test_keras_testing_util_layer_test_1D(kernel_cls, batch_size, n_components):
kernel = kernel_cls()

Expand All @@ -180,6 +182,7 @@ def test_keras_testing_util_layer_test_1D(kernel_cls, batch_size, n_components):
)


@skip_serialization_tests
def test_keras_testing_util_layer_test_multidim(kernel_cls, batch_size, n_dims, n_components):
kernel = kernel_cls()

Expand Down
7 changes: 4 additions & 3 deletions tests/gpflux/layers/test_gp_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from gpflux.helpers import construct_basic_inducing_variables, construct_basic_kernel
from gpflux.layers import GPLayer
from gpflux.types import unwrap_dist


def setup_gp_layer_and_data(num_inducing: int, **gp_layer_kwargs):
Expand Down Expand Up @@ -97,19 +98,19 @@ def test_call_shapes():
assert not gp_layer.full_cov and not gp_layer.full_output_cov

distribution = gp_layer(X, training=False)
assert isinstance(distribution, tfp.distributions.MultivariateNormalDiag)
assert isinstance(unwrap_dist(distribution), tfp.distributions.MultivariateNormalDiag)
assert distribution.shape == (batch_size, output_dim)

gp_layer.full_cov = True
distribution = gp_layer(X, training=False)
assert isinstance(distribution, tfp.distributions.MultivariateNormalTriL)
assert isinstance(unwrap_dist(distribution), tfp.distributions.MultivariateNormalTriL)
assert distribution.shape == (batch_size, output_dim)
assert distribution.covariance().shape == (output_dim, batch_size, batch_size)

gp_layer.full_output_cov = True
gp_layer.full_cov = False
distribution = gp_layer(X, training=False)
assert isinstance(distribution, tfp.distributions.MultivariateNormalTriL)
assert isinstance(unwrap_dist(distribution), tfp.distributions.MultivariateNormalTriL)
assert distribution.shape == (batch_size, output_dim)
assert distribution.covariance().shape == (batch_size, output_dim, output_dim)

Expand Down
2 changes: 1 addition & 1 deletion tests/gpflux/layers/test_latent_variable_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@


def _zero_one_normal_prior(w_dim):
""" N(0, I) prior """
"""N(0, I) prior"""
return tfp.distributions.MultivariateNormalDiag(loc=np.zeros(w_dim), scale_diag=np.ones(w_dim))


Expand Down
16 changes: 9 additions & 7 deletions tests/gpflux/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import numpy as np
import pytest
import tensorflow as tf
from packaging.version import Version

import gpflow

Expand All @@ -39,7 +40,7 @@ class CONFIG:

@pytest.fixture
def data() -> Tuple[np.ndarray, np.ndarray]:
""" Step function: f(x) = -1 for x <= 0 and 1 for x > 0. """
"""Step function: f(x) = -1 for x <= 0 and 1 for x > 0."""
X = np.linspace(-1, 1, CONFIG.num_data)
Y = np.where(X > 0, np.ones_like(X), -np.ones_like(X))
return (X.reshape(-1, 1), Y.reshape(-1, 1))
Expand Down Expand Up @@ -135,12 +136,13 @@ def test_tensorboard_callback(tmp_path, model_and_loss, data, update_freq):
"self_tracked_trackables[3].likelihood.variance",
}

if update_freq == "batch":
expected_tags |= {
"batch_loss",
"batch_gp0_prior_kl",
"batch_gp1_prior_kl",
}
if Version(tf.__version__) < Version("2.8"):
if update_freq == "batch":
expected_tags |= {
"batch_loss",
"batch_gp0_prior_kl",
"batch_gp1_prior_kl",
}

# Check all model variables, loss and lr are in tensorboard.
assert set(records.keys()) == expected_tags
Expand Down
13 changes: 8 additions & 5 deletions tests_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Code quality tools:
black==20.8b1
black==21.7b0
codecov
click==8.0.2
flake8==3.8.4
isort==5.6.4
mypy==0.770
click==8.0.4
flake8==4.0.1
isort==5.10.1
mypy==0.921
pytest
pytest-cov
pytest-random-order
pytest-mock

# For mypy stubs:
types-Deprecated

tqdm

# Notebook tests:
Expand Down

0 comments on commit 02268eb

Please sign in to comment.