Skip to content

Add optional dependency management #537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d8d6c6b
Add optional dependency functionality and tests
mauicv May 10, 2022
2915ccc
Add tox envs and test stubs
mauicv May 11, 2022
702c11e
Update CONTRIBUTING.md
mauicv May 11, 2022
317806a
Make numba a cor-dep, will be reverted in later PR
mauicv May 11, 2022
0b8f869
Add correct error messages for multiple optional dependency objects
mauicv May 16, 2022
a56e6ad
Set license checks for just default dependencies
mauicv Jun 20, 2022
fc7618b
Merge tensorflow and tensorflow-prob into single bucket
mauicv Jun 30, 2022
1196c3b
Fix flake8 errors and remove redundant test
mauicv Jun 30, 2022
642de25
Minor fix
mauicv Jun 30, 2022
a7c883d
Feature Optional backends functionality (#538)
mauicv Jul 7, 2022
99d697a
remove numba optional dep
mauicv Jul 13, 2022
94aaf32
Remove redundant code in import_optional
mauicv Jul 18, 2022
0df1ba6
Update suggested changes from PR review
mauicv Jul 18, 2022
3d7a122
Remove unnecessary checks in test_dep_management.py
mauicv Jul 18, 2022
8410242
Fix flake8 issues
mauicv Jul 18, 2022
026133f
Merge config into opt deps (#553)
mauicv Jul 21, 2022
8407df4
Add tox tests to ci
mauicv Jul 21, 2022
2ff55ae
Add tensorflow to changed_notebook_tests
mauicv Jul 22, 2022
b9385d7
Feature Optional prophet (#541)
mauicv Jul 22, 2022
5bc3119
Feature Optional dependencies documentation updates (#542)
mauicv Jul 22, 2022
60b97c6
Update licenses (#562)
mauicv Jul 22, 2022
ece444f
Rename alibi-> alibi-detect in missing_optional_dependency
mauicv Jul 25, 2022
1e211c4
Add further comments to backend validator
mauicv Jul 25, 2022
9c31d66
Make the bakcend validator comments clearer
mauicv Jul 25, 2022
5e8ed81
Remove unnessery __init__.py in alibi_detect/utils/tests
mauicv Jul 25, 2022
67c81b4
Merge branch 'master' into feature/dep-management
mauicv Jul 25, 2022
b9458eb
Add updates to CHANGELOG for optional dependencies (#563)
mauicv Jul 25, 2022
3375873
Merge branch 'master' into feature/dep-management
mauicv Jul 25, 2022
6426b61
Fix licenses
mauicv Jul 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
if [ "$RUNNER_OS" != "Windows" ] && [ ${{ matrix.python }} < '3.10' ]; then # Skip Prophet tests on Windows as installation complex. Skip on Python 3.10 as not supported.
python -m pip install --upgrade --upgrade-strategy eager -e .[prophet]
fi
python -m pip install --upgrade --upgrade-strategy eager -e .[torch]
python -m pip install --upgrade --upgrade-strategy eager -e .[tensorflow,torch]
python -m pip freeze

- name: Lint with flake8
Expand Down
60 changes: 60 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,63 @@ the docs will be built under `doc/_build/html`.

## CI
All PRs triger a Github Actions build to run linting, type checking, tests, and build docs.

## Optional Dependencies

Alibi-detect uses optional dependencies to allow users to avoid installing large or challenging to install dependencies.
Alibi-detect manages modularity of components that depend on optional dependencies using the `import_optional` function
defined in `alibi_detect/utils/missing_optional_dependency.py`. This replaces the dependency with a dummy object that
raises an error when called. If you are working on public functionality that is dependent on an optional dependency you
should expose the functionality via the relevant `__init__.py` file by importing it there using the `optional_import`
function. Currently, optional dependencies are tested by importing all the public functionality and checking that the
correct errors are raised dependent on the environment. Developers can run these tests using `tox`. These tests are in
`alibi_detect/tests/test_dep_mangement.py`. If implementing functionality that is dependent on a new optional dependency
then you will need to:

1. Add it to `extras_require` in `setup.py`.
2. Create a new `tox` environment in `setup.cfg` with the new dependency.
3. Add a new dependency mapping to `ERROR_TYPES` in `alibi_detect/utils/missing_optional_dependency.py`.
4. Make sure any public functionality is protected by the `import_optional` function.
5. Make sure the new dependency is tested in `alibi_detect/tests/test_dep_mangement.py`.

Note that subcomponents can be dependent on optional dependencies too. In this case the user should be able to import
and use the relevant parent component. The user should only get an error message if:
1. They don't have the optional dependency installed and,
2. They configure the parent component in such as way that it uses the subcomponent functionality.

Developers should implement this by importing the subcomponent into the source code defining the parent component using
the `optional_import` function. The general layout of a subpackage with optional dependencies should look like:

```
alibi_detect/subpackage/
__init__.py # expose public API with optional import guards
defaults.py # private implementations requiring only core deps
optional_dep.py # private implementations requiring an optional dependency (or several?)
```

any public functionality that is dependent on an optional dependency should be imported into `__init__.py` using the
`import_optional` function.

#### Note:
- The `import_optional` function returns an instance of a class and if this is passed to type-checking constructs, such
as Union, it will raise errors. Thus, in order to do type-checking, we need to 1. Conditionally import the true object
dependent on `TYPE_CHECKING` and 2. Use forward referencing when passing to typing constructs such as `Union`. We use
forward referencing because in a user environment the optional dependency may not be installed in which case it'll be
replaced with an instance of the MissingDependency class. For example:
```py
from typing import TYPE_CHECKING, Union

if TYPE_CHECKING:
# Import for type checking. This will be type `UsefulClass`. Note import is from implementation file.
from alibi_detect.utils.useful_classes import UsefulClass
else:
# Import is from `__init__` public API file. Class will be protected by optional_import function and so this will
# be type any.
from alibi.utils import UsefulClass

# The following will not throw an error because of the forward reference but mypy will still work.
def example_function(useful_class: Union['UsefulClass', str]) -> None:
...
```
- Developers can use `make repl tox-env=<tox-env-name>` to run a python REPL with the specified optional dependency
installed. This is to allow manual testing.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,7 @@ licenses:
check_licenses:
git --no-pager diff --exit-code ./licenses/license_info.no_versions.csv


.PHONY: repl
tox-env=default
repl:
env COMMAND="python" tox -e $(tox-env)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ We will use the [VAE outlier detector](https://docs.seldon.io/projects/alibi-det

```python
from alibi_detect.od import OutlierVAE
from alibi_detect.utils import save_detector, load_detector
from alibi_detect.utils.saving import save_detector, load_detector

# initialize and fit detector
od = OutlierVAE(threshold=0.1, encoder_net=encoder_net, decoder_net=decoder_net, latent_dim=1024)
Expand Down
6 changes: 4 additions & 2 deletions alibi_detect/ad/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from .adversarialae import AdversarialAE
from .model_distillation import ModelDistillation
from alibi_detect.utils.missing_optional_dependency import import_optional

AdversarialAE = import_optional('alibi_detect.ad.adversarialae', names=['AdversarialAE'])
ModelDistillation = import_optional('alibi_detect.ad.model_distillation', names=['ModelDistillation'])

__all__ = [
"AdversarialAE",
Expand Down
21 changes: 11 additions & 10 deletions alibi_detect/cd/classifier.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import numpy as np
from typing import Callable, Dict, Optional, Union
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, has_sklearn
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, \
BackendValidator
from alibi_detect.base import DriftConfigMixin

if has_sklearn:
from sklearn.base import ClassifierMixin
from alibi_detect.cd.sklearn.classifier import ClassifierDriftSklearn

from sklearn.base import ClassifierMixin
from alibi_detect.cd.sklearn.classifier import ClassifierDriftSklearn

if has_pytorch:
from torch.utils.data import DataLoader
Expand Down Expand Up @@ -147,12 +148,12 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if (backend == 'tensorflow' and not has_tensorflow) or (backend == 'pytorch' and not has_pytorch) or \
(backend == 'sklearn' and not has_sklearn):
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'ClassifierDrift detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch', 'sklearn']:
raise NotImplementedError(f"{backend} not implemented. Use 'tensorflow', 'pytorch' or 'sklearn' instead.")
BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch'],
Copy link
Contributor

@jklaise jklaise Jul 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be 'pytorch': ['torch'] ? check elsewhere where BackendValidator is invoked too and the docstring for BackendValidator also.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dependencies' presence is checked by the HAS_BACKEND dict which has key pytorch. The pip install message on the other hand is generated from the ERROR_TYPES which maps to torch which is also the name of our dependency bucket. So the error message is:

ImportError: `pytorch` not installed. Cannot initialize and run LSDDDrift with pytorch backend. The necessary missing dependencies can be installed using `pip install alibi-detect[torch]`.

I kept this in line with the error messages that were being generated prior to the addition of the BackendValidator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok makes sense, but probably would be great to add some comments in the code around this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mauicv did you add the comments @jklaise is referring to here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the comment here to be clearer about the behaviour!

'sklearn': ['sklearn']},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly 'sklearn': ['scikit-learn']? Although I tested this and it seems they aliased sklearn to pull in scikit-learn.

Copy link
Contributor

@jklaise jklaise Jul 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, scikit-learn is a core dependency, not sure why this check is even here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied the functionality from here and here. But yeah, I don't think it makes sense to have the full import check so I've just set HAS_BACKEND['sklearnn] = True. I think It is simpler just to keep the general logic in place even though it's redundant. Although happy to change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about your comment about the full import check vs setting the boolean flags, probably would need to see the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just that here we try and import sklearn and then set has_sklearn conditional on the result which is redundant as sklearn should always be there!

construct_name=self.__class__.__name__
).verify_backend(backend)

kwargs = locals()
args = [kwargs['x_ref'], kwargs['model']]
Expand Down
12 changes: 6 additions & 6 deletions alibi_detect/cd/context_aware.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import numpy as np
from typing import Callable, Dict, Optional, Union, Tuple
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator
from alibi_detect.utils.warnings import deprecated_alias
from alibi_detect.base import DriftConfigMixin

Expand Down Expand Up @@ -92,11 +92,11 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'ContextMMMDrift detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch']:
raise NotImplementedError(f'{backend} not implemented. Use tensorflow or pytorch instead.')
BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch']},
construct_name=self.__class__.__name__
).verify_backend(backend)

kwargs = locals()
args = [kwargs['x_ref'], kwargs['c_ref']]
Expand Down
12 changes: 6 additions & 6 deletions alibi_detect/cd/learned_kernel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
from typing import Callable, Dict, Optional, Union
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator
from alibi_detect.utils.warnings import deprecated_alias
from alibi_detect.base import DriftConfigMixin

Expand Down Expand Up @@ -121,11 +121,11 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'LearnedKernel detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch']:
raise NotImplementedError(f'{backend} not implemented. Use tensorflow or pytorch instead.')
BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch']},
construct_name=self.__class__.__name__
).verify_backend(backend)

kwargs = locals()
args = [kwargs['x_ref'], kwargs['kernel']]
Expand Down
12 changes: 6 additions & 6 deletions alibi_detect/cd/lsdd.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
from typing import Callable, Dict, Optional, Union, Tuple
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator
from alibi_detect.utils.warnings import deprecated_alias
from alibi_detect.base import DriftConfigMixin

Expand Down Expand Up @@ -81,11 +81,11 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'LSDDDrift detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch']:
raise NotImplementedError(f'{backend} not implemented. Use tensorflow or pytorch instead.')
BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch']},
construct_name=self.__class__.__name__
).verify_backend(backend)

kwargs = locals()
args = [kwargs['x_ref']]
Expand Down
12 changes: 6 additions & 6 deletions alibi_detect/cd/lsdd_online.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
from typing import Any, Callable, Dict, Optional, Union
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator
from alibi_detect.base import DriftConfigMixin
if has_pytorch:
from alibi_detect.cd.pytorch.lsdd_online import LSDDDriftOnlineTorch
Expand Down Expand Up @@ -82,11 +82,11 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'MMDDrift detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch']:
raise NotImplementedError(f'{backend} not implemented. Use tensorflow or pytorch instead.')
BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch']},
construct_name=self.__class__.__name__
).verify_backend(backend)

kwargs = locals()
args = [kwargs['x_ref'], kwargs['ert'], kwargs['window_size']]
Expand Down
12 changes: 6 additions & 6 deletions alibi_detect/cd/mmd.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import numpy as np
from typing import Callable, Dict, Optional, Union, Tuple
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator
from alibi_detect.utils.warnings import deprecated_alias
from alibi_detect.base import DriftConfigMixin

Expand Down Expand Up @@ -80,11 +80,11 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'MMDDrift detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch']:
raise NotImplementedError(f'{backend} not implemented. Use tensorflow or pytorch instead.')
BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch']},
construct_name=self.__class__.__name__
).verify_backend(backend)

kwargs = locals()
args = [kwargs['x_ref']]
Expand Down
12 changes: 6 additions & 6 deletions alibi_detect/cd/mmd_online.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
from typing import Any, Callable, Dict, Optional, Union
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator
from alibi_detect.base import DriftConfigMixin

if has_pytorch:
Expand Down Expand Up @@ -75,11 +75,11 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'MMDDrift detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch']:
raise NotImplementedError(f'{backend} not implemented. Use tensorflow or pytorch instead.')
BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch']},
construct_name=self.__class__.__name__
).verify_backend(backend)

kwargs = locals()
args = [kwargs['x_ref'], kwargs['ert'], kwargs['window_size']]
Expand Down
20 changes: 13 additions & 7 deletions alibi_detect/cd/model_uncertainty.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from alibi_detect.cd.chisquare import ChiSquareDrift
from alibi_detect.cd.preprocess import classifier_uncertainty, regressor_uncertainty
from alibi_detect.cd.utils import encompass_batching, encompass_shuffling_and_batch_filling
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import BackendValidator
from alibi_detect.base import DriftConfigMixin

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -83,9 +83,12 @@ def __init__(
# Set config
self._set_config(locals())

if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'ClassifierUncertaintyDrift detector with {backend} backend.')
if backend:
backend = backend.lower()
BackendValidator(backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch'],
None: []},
construct_name=self.__class__.__name__).verify_backend(backend)

if backend is None:
if device not in [None, 'cpu']:
Expand Down Expand Up @@ -233,9 +236,12 @@ def __init__(
# Set config
self._set_config(locals())

if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'RegressorUncertaintyDrift detector with {backend} backend.')
if backend:
backend = backend.lower()
BackendValidator(backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch'],
None: []},
construct_name=self.__class__.__name__).verify_backend(backend)

if backend is None:
model_fn = model
Expand Down
6 changes: 5 additions & 1 deletion alibi_detect/cd/pytorch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from .preprocess import HiddenOutput, preprocess_drift
from alibi_detect.utils.missing_optional_dependency import import_optional

HiddenOutput, preprocess_drift = import_optional(
'alibi_detect.cd.pytorch.preprocess',
names=['HiddenOutput', 'preprocess_drift'])

__all__ = [
"HiddenOutput",
Expand Down
2 changes: 1 addition & 1 deletion alibi_detect/cd/pytorch/context_aware.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,5 +274,5 @@ def _sigma_median_diag(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) ->
The computed bandwidth, `sigma`.
"""
n_median = np.prod(dist.shape) // 2
sigma = (.5 * dist.flatten().sort().values[n_median].unsqueeze(dim=-1)) ** .5
sigma = (.5 * dist.flatten().sort().values[int(n_median)].unsqueeze(dim=-1)) ** .5
return sigma
13 changes: 6 additions & 7 deletions alibi_detect/cd/spot_the_diff.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
from typing import Callable, Dict, Optional, Union
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow
from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator
from alibi_detect.base import DriftConfigMixin

if has_pytorch:
Expand Down Expand Up @@ -126,12 +126,11 @@ def __init__(
self._set_config(locals())

backend = backend.lower()
if backend == 'tensorflow' and not has_tensorflow or backend == 'pytorch' and not has_pytorch:
raise ImportError(f'{backend} not installed. Cannot initialize and run the '
f'SpotTheDiffDrift detector with {backend} backend.')
elif backend not in ['tensorflow', 'pytorch']:
raise NotImplementedError(f'{backend} not implemented. Use tensorflow or pytorch instead.')

BackendValidator(
backend_options={'tensorflow': ['tensorflow'],
'pytorch': ['pytorch']},
construct_name=self.__class__.__name__
).verify_backend(backend)
kwargs = locals()
args = [kwargs['x_ref']]
pop_kwargs = ['self', 'x_ref', 'backend', '__class__']
Expand Down
7 changes: 6 additions & 1 deletion alibi_detect/cd/tensorflow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from .preprocess import HiddenOutput, UAE, preprocess_drift
from alibi_detect.utils.missing_optional_dependency import import_optional

HiddenOutput, UAE, preprocess_drift = import_optional(
'alibi_detect.cd.tensorflow.preprocess',
names=['HiddenOutput', 'UAE', 'preprocess_drift']
)

__all__ = [
"HiddenOutput",
Expand Down
2 changes: 1 addition & 1 deletion alibi_detect/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pandas as pd
import requests
from alibi_detect.utils.data import Bunch
from alibi_detect.utils.fetching import _join_url
from alibi_detect.utils.url import _join_url
from requests import RequestException
from scipy.io import arff
from sklearn.datasets import fetch_kddcup99
Expand Down
Loading