forked from facebookresearch/aepsych
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor inducing point allocator classes to only need what they need (…
…facebookresearch#478) Summary: Pull Request resolved: facebookresearch#478 Inducing point allocator classes had extra methods and attributes that were not necessary or misnamed. These have been cleaned up in the BaseAllocator class and all of its children are similarly updated. The dummy allocator is no longer needed, instead, the allocators can just make dummy points based on this dimensionality. This sets the last_allocator_used attribute to be None to signify no actual allocator was used in creating the last set of points. This also requires all allocators to know its dimensionality at least as it is used by the dummy allocator when there's no inputs. All models that use an allocator now initializes the allocators outside as there's no trivial default for allocators anymore. Differential Revision: D67059839
- Loading branch information
1 parent
c3a8421
commit 07bb7b1
Showing
12 changed files
with
415 additions
and
815 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,123 +1,42 @@ | ||
from typing import Any, Dict, Optional | ||
from typing import Optional | ||
|
||
import torch | ||
from aepsych.config import Config | ||
from aepsych.models.inducing_points.base import BaseAllocator, DummyAllocator | ||
from aepsych.models.inducing_points.base import BaseAllocator | ||
from aepsych.models.inducing_points.kmeans import KMeansAllocator | ||
from botorch.models.utils.inducing_point_allocators import InducingPointAllocator | ||
|
||
|
||
class AutoAllocator(BaseAllocator): | ||
"""An inducing point allocator that dynamically chooses an allocation strategy | ||
based on the number of unique data points available.""" | ||
|
||
def __init__( | ||
self, | ||
bounds: Optional[torch.Tensor] = None, | ||
fallback_allocator: InducingPointAllocator = KMeansAllocator(), | ||
) -> None: | ||
""" | ||
Initialize the AutoAllocator with a fallback allocator. | ||
Args: | ||
fallback_allocator (InducingPointAllocator, optional): Allocator to use if there are | ||
more unique points than required. | ||
""" | ||
super().__init__(bounds=bounds) | ||
self.fallback_allocator = fallback_allocator | ||
if bounds is not None: | ||
self.bounds = bounds | ||
self.dummy_allocator = DummyAllocator(bounds=bounds) | ||
|
||
def _get_quality_function(self) -> None: | ||
"""AutoAllocator does not require a quality function, so this returns None.""" | ||
return None | ||
|
||
def allocate_inducing_points( | ||
self, | ||
inputs: Optional[torch.Tensor], | ||
inputs: Optional[torch.Tensor] = None, | ||
covar_module: Optional[torch.nn.Module] = None, | ||
num_inducing: int = 10, | ||
num_inducing: int = 100, | ||
input_batch_shape: torch.Size = torch.Size([]), | ||
) -> torch.Tensor: | ||
""" | ||
Allocate inducing points by either using the unique input data directly | ||
or falling back to another allocation method if there are too many unique points. | ||
"""Generates `num_inducing` inducing points smartly based on the inputs. | ||
Currently, this is just a wrapper for the KMeansAllocator | ||
Args: | ||
inputs (torch.Tensor): A tensor of shape (n, d) containing the input data. | ||
covar_module (torch.nn.Module, optional): Kernel covariance module; included for API compatibility, but not used here. | ||
num_inducing (int, optional): The number of inducing points to generate. | ||
num_inducing (int, optional): The number of inducing points to generate. Defaults to 100. | ||
input_batch_shape (torch.Size, optional): Batch shape, defaults to an empty size; included for API compatibility, but not used here. | ||
Returns: | ||
torch.Tensor: A (num_inducing, d)-dimensional tensor of inducing points. | ||
torch.Tensor: A (num_inducing, d)-dimensional tensor of inducing points selected via k-means++. | ||
""" | ||
# Ensure inputs are not None | ||
if inputs is None and self.bounds is not None: | ||
self.allocator_used = self.dummy_allocator.__class__.__name__ | ||
return self.dummy_allocator.allocate_inducing_points( | ||
inputs=inputs, | ||
covar_module=covar_module, | ||
num_inducing=num_inducing, | ||
input_batch_shape=input_batch_shape, | ||
) | ||
elif inputs is None and self.bounds is None: | ||
raise ValueError(f"Either inputs or bounds must be provided.{self.bounds}") | ||
|
||
assert ( | ||
inputs is not None | ||
), "inputs should not be None here" # to make mypy happy | ||
|
||
unique_inputs = torch.unique(inputs, dim=0) | ||
|
||
# If there are fewer unique points than required, return unique inputs directly | ||
if unique_inputs.shape[0] <= num_inducing: | ||
self.allocator_used = self.__class__.__name__ | ||
return unique_inputs | ||
|
||
# Otherwise, fall back to the provided allocator (e.g., KMeansAllocator) | ||
if inputs.shape[0] <= num_inducing: | ||
self.allocator_used = self.__class__.__name__ | ||
return inputs | ||
else: | ||
self.allocator_used = self.fallback_allocator.__class__.__name__ | ||
return self.fallback_allocator.allocate_inducing_points( | ||
inputs=inputs, | ||
covar_module=covar_module, | ||
num_inducing=num_inducing, | ||
input_batch_shape=input_batch_shape, | ||
) | ||
|
||
@classmethod | ||
def get_config_options( | ||
cls, | ||
config: Config, | ||
name: Optional[str] = None, | ||
options: Optional[Dict[str, Any]] = None, | ||
) -> Dict[str, Any]: | ||
"""Get configuration options for the AutoAllocator. | ||
Args: | ||
config (Config): Configuration object. | ||
name (str, optional): Name of the allocator, defaults to None. | ||
options (Dict[str, Any], optional): Additional options, defaults to None. | ||
Returns: | ||
Dict[str, Any]: Configuration options for the AutoAllocator. | ||
""" | ||
if name is None: | ||
name = cls.__name__ | ||
lb = config.gettensor("common", "lb") | ||
ub = config.gettensor("common", "ub") | ||
bounds = torch.stack((lb, ub)) | ||
fallback_allocator_cls = config.getobj( | ||
name, "fallback_allocator", fallback=KMeansAllocator | ||
) | ||
fallback_allocator = ( | ||
fallback_allocator_cls.from_config(config) | ||
if hasattr(fallback_allocator_cls, "from_config") | ||
else fallback_allocator_cls() | ||
# Auto allocator actually just wraps the Kmeans allocator | ||
allocator = KMeansAllocator(bounds=self.bounds, dim=self.dim) | ||
|
||
points = allocator.allocate_inducing_points( | ||
inputs=inputs, | ||
covar_module=covar_module, | ||
num_inducing=num_inducing, | ||
input_batch_shape=input_batch_shape, | ||
) | ||
|
||
return {"fallback_allocator": fallback_allocator, "bounds": bounds} | ||
self.last_allocator_used = allocator.last_allocator_used | ||
return points |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.