Skip to content
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

Support for serialising detectors with keops backends #681

Merged
merged 9 commits into from
Jan 3, 2023
Merged

Support for serialising detectors with keops backends #681

merged 9 commits into from
Jan 3, 2023

Conversation

ascillitoe
Copy link
Contributor

Following on from #656, this PR adds support for serialising detectors with backend='keops'. Since keops detectors are still pytorch based, this is a relatively simple addition. The main changes involve removing NotImplementedError's and support for saving keops kernels.

@ascillitoe ascillitoe added Type: Serialization Serialization proposals and changes WIP PR is a Work in Progress labels Nov 23, 2022
@ascillitoe ascillitoe added this to the v0.11.0 milestone Nov 23, 2022
@ascillitoe ascillitoe removed the WIP PR is a Work in Progress label Nov 23, 2022
@codecov-commenter
Copy link

codecov-commenter commented Nov 23, 2022

Codecov Report

Merging #681 (35e3025) into master (9a344ae) will increase coverage by 0.39%.
The diff coverage is 96.55%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #681      +/-   ##
==========================================
+ Coverage   79.76%   80.16%   +0.39%     
==========================================
  Files         131      133       +2     
  Lines        9133     9176      +43     
==========================================
+ Hits         7285     7356      +71     
+ Misses       1848     1820      -28     
Flag Coverage Δ
macos-latest-3.10 76.66% <20.68%> (-0.29%) ⬇️
ubuntu-latest-3.10 80.05% <96.55%> (+0.40%) ⬆️
ubuntu-latest-3.7 79.95% <96.42%> (+0.39%) ⬆️
ubuntu-latest-3.8 80.00% <96.55%> (+0.40%) ⬆️
ubuntu-latest-3.9 80.00% <96.55%> (+0.40%) ⬆️
windows-latest-3.9 76.59% <20.68%> (-0.29%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
alibi_detect/saving/saving.py 87.44% <ø> (+0.54%) ⬆️
alibi_detect/saving/_keops/loading.py 88.23% <88.23%> (ø)
alibi_detect/saving/_keops/__init__.py 100.00% <100.00%> (ø)
alibi_detect/saving/loading.py 93.71% <100.00%> (+0.48%) ⬆️
alibi_detect/saving/registry.py 100.00% <100.00%> (ø)
alibi_detect/saving/schemas.py 98.77% <100.00%> (ø)
alibi_detect/saving/validators.py 90.38% <100.00%> (ø)
alibi_detect/utils/keops/kernels.py 98.01% <100.00%> (+0.61%) ⬆️
alibi_detect/cd/keops/mmd.py 98.21% <0.00%> (+1.78%) ⬆️
alibi_detect/cd/keops/learned_kernel.py 94.16% <0.00%> (+18.97%) ⬆️

Copy link
Contributor

@jklaise jklaise left a comment

Choose a reason for hiding this comment

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

A few minor comments but otherwise LGTM!

Comment on lines 124 to 126
cfg = self.config.copy()
if isinstance(cfg['sigma'], torch.Tensor):
cfg['sigma'] = cfg['sigma'].detach().cpu().numpy().tolist()
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we certain we don't need a deep copy here (depends on the dict values)? Wouldn't be ideal if self.config turns out to not be idempotent (different depending whether get_config has been called or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the same question applies to every component for which we have self.config and which may have mutable values - do we need to do an audit / have tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Upon review of this I think you're right. We seem to get away with it in our tests (and in the informal tests I did previously) because when we update items in config we are typically redefining the entire item, rather than mutating it (e.g. we change sigma from None to a Tensor, rather than mutating an element in sigma). I think this means the shallow copy is OK since the reference is broken, e.g.:

orig_a = [1,2,3]
dict1 = {'a': deepcopy(orig_a), 'b': 'cat'}
dict2 = dict1.copy()

dict1['a'] = [1,1,1]

dict2['a'] == orig_a

>>> True

However, as you say, if we were to mutate an item in config, we would have a problem:

orig_a = [1,2,3]
dict1 = {'a': deepcopy(orig_a), 'b': 'cat'}
dict2 = dict1.copy()

dict1['a'][1] = 1 

dict2['a'] == orig_a

>>> False

I might be missing something but seems we should add deepcopy to all of these. I can update these ones in this PR and then do a follow-up for the rest of them?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea I think better safe than sorry.

Copy link
Contributor Author

@ascillitoe ascillitoe Nov 24, 2022

Choose a reason for hiding this comment

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

Done for keops kernel only in 669a8c6

Tracker for the remaining changes: #683

Comment on lines 148 to 149
kernel_a: Union[nn.Module, str] = 'rbf',
kernel_b: Optional[Union[nn.Module, str]] = 'rbf',
Copy link
Contributor

Choose a reason for hiding this comment

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

I would say to use Literals but I know you'll point me to the other open issue... :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ha ha I hope I'm not developing a reputation for deflecting to other issues!

I can see why Literal might be a good idea here since we really do only accept rbf as a str. Since we'd want to do this for the pytorch and tensorflow kernels it might be best for me to follow-up straight after this PR?

Copy link
Contributor Author

@ascillitoe ascillitoe Nov 24, 2022

Choose a reason for hiding this comment

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

Changed to Literal for keops kernel only in 35e3025.

Tracker for wider changes: #683

@@ -176,3 +205,10 @@ def forward(self, x_proj: LazyTensor, y_proj: LazyTensor, x: Optional[LazyTensor
if self.kernel_b is not None:
similarity = (1-self.eps)*similarity + self.eps*self.kernel_b(x, y)
return similarity

def get_config(self) -> dict:
return self.config.copy()
Copy link
Contributor

Choose a reason for hiding this comment

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

No flavour 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.

Bear with me on this one! We have flavour in ModelConfig etc (see saving.schemas) because we use this to decide which load_model_... function to use (i.e. _tf, _pt or _sk). I've kept flavour decoupled from the detector backend here so that we have the option of using different flavour preprocessing to the model backend (e.g. a scikit-learn preprocessor with a tensorflow MMD detector). Plus, we might have a model even when there is no backend (e.g. any preprocessing model with KS detector).

The situation is a little different for kernels. These are only used for detectors which have backends, and it doesn't make sense to decouple their flavour from backend. Ideally we would not actually have flavour at all for kernels (we actually decide which load_kernel_... function to use based on the detector backend. The only reason we have flavour is so that the coerce_2_tensor pydantic validator knows what type of Tensor to coerce sigma to (this happens before we get to to load_kernel_....

We don't need to do any coercion for DeepKernel hence why no flavour in DeepKernelConfig. I could add it in just for consistency? But it wouldn't actually be used at load time.

@@ -25,6 +25,10 @@
from alibi_detect.models.tensorflow import TransformerEmbedding as TransformerEmbedding_tf
from alibi_detect.cd.pytorch import HiddenOutput as HiddenOutput_pt
from alibi_detect.cd.tensorflow import HiddenOutput as HiddenOutput_tf
from alibi_detect.utils.frameworks import has_keops
if has_keops:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this conditional purely because we skip keops on some platforms in CI? Should we add a comment for this? Same question for the other test module.

Copy link
Contributor Author

@ascillitoe ascillitoe Nov 24, 2022

Choose a reason for hiding this comment

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

Yep precisely. I'll add notes to both modules now!

Edit: Done in 5bb7314

Copy link
Collaborator

@mauicv mauicv left a comment

Choose a reason for hiding this comment

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

LGTM

@ascillitoe
Copy link
Contributor Author

@jklaise would you be able to look into the responses to your comments when you have a chance, please?

Copy link
Contributor

@jklaise jklaise left a comment

Choose a reason for hiding this comment

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

LGTM

@ascillitoe ascillitoe merged commit 83b5d2f into SeldonIO:master Jan 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Serialization Serialization proposals and changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants