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

Release/v0.2.0 #274

Merged
merged 10 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Changelog

## [0.2.0] - 2024-11-25

- Added Conditional and Attentive Neural Processes
- Added Gaussian Processes and Multi-task Gaussian Processes with GPyTorch
- Changed parts of the UI:
- print_results() is now summarise_cv()
- plot_results() is now plot_cv()
- evaluate_model() is now evaluate()
- plot_model() is now plot_eval()
- Added Global Sensitivity Analysis
- New visualisation: Xy plot with confidence bands
- Bayesian Hyperparameter Optimization has been removed
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ The project is in early development.

## Installation

There's currently a lot of development, so we recommend installing the most current version from GitHub:
There's lots of development at the moment, so we recommend installing the most current version from GitHub:

```bash
pip install git+https://github.com/alan-turing-institute/autoemulate.git
```

There's also a release available on PyPI (note: currently an older version and out of date with the documentation)
There's also a release onPyPI:

```bash
pip install autoemulate
```
Expand Down
14 changes: 6 additions & 8 deletions autoemulate/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,9 +479,7 @@ def evaluate(self, model=None, multioutput="uniform_average"):

scores_df = (
pd.DataFrame(scores)
.assign(
target=[f"target_{i}" for i in range(len(scores[next(iter(scores))]))]
)
.assign(target=[f"y{i}" for i in range(len(scores[next(iter(scores))]))])
.assign(short=get_short_model_name(model))
.assign(model=get_model_name(model))
.reindex(columns=["model", "short", "target"] + list(scores.keys()))
Expand All @@ -508,17 +506,17 @@ def plot_eval(
----------
model : object
Fitted model.
plot_type : str, optional
style : str, optional
The type of plot to draw:
"Xy" observed and predicted values vs. features, including 2σ error bands where available (default).
"actual_vs_predicted" draws the observed values (y-axis) vs. the predicted values (x-axis) (default).
"residual_vs_predicted" draws the residuals, i.e. difference between observed and predicted values, (y-axis) vs. the predicted values (x-axis).
n_cols : int, optional
Number of columns in the plot grid for multi-output. Default is 2.
output_index : int
Index of the output to plot. Default is 0..
input_index : int
Index of the input to plot. Default is 0. Only used if plot_type="Xy".
output_index : list, int
Index of the output to plot. Either a single index or a list of indices.
input_index : list, int
Index of the input to plot. Either a single index or a list of indices. Only used if style="Xy".
"""
fig = _plot_model(
model,
Expand Down
169 changes: 90 additions & 79 deletions autoemulate/emulators/gaussian_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,85 +221,6 @@ def predict(self, X, return_std=False):
def get_grid_params(self, search_type="random"):
"""Returns the grid parameters for the emulator."""

def rbf(n_features, n_outputs):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
).initialize(lengthscale=torch.ones(n_features) * 1.5)

def matern_5_2_kernel(n_features, n_outputs):
return gpytorch.kernels.MaternKernel(
nu=2.5,
ard_num_dims=n_features,
batch_shape=n_outputs,
)

def matern_3_2_kernel(n_features, n_outputs):
return gpytorch.kernels.MaternKernel(
nu=1.5,
ard_num_dims=n_features,
batch_shape=n_outputs,
)

def rq_kernel(n_features, n_outputs):
return gpytorch.kernels.RQKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)

def rbf_plus_constant(n_features, n_outputs):
return (
gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
).initialize(lengthscale=torch.ones(n_features) * 1.5)
+ gpytorch.kernels.ConstantKernel()
)

# combinations
def rbf_plus_linear(n_features, n_outputs):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
) + gpytorch.kernels.LinearKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)

def matern_5_2_plus_rq(n_features, n_outputs):
return gpytorch.kernels.MaternKernel(
nu=2.5,
ard_num_dims=n_features,
batch_shape=n_outputs,
) + gpytorch.kernels.RQKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)

def rbf_times_linear(n_features, n_outputs):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
) * gpytorch.kernels.LinearKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)

# means
def constant_mean(n_features, n_outputs):
return gpytorch.means.ConstantMean(batch_shape=n_outputs)

def zero_mean(n_features, n_outputs):
return gpytorch.means.ZeroMean(batch_shape=n_outputs)

def linear_mean(n_features, n_outputs):
return gpytorch.means.LinearMean(
input_size=n_features, batch_shape=n_outputs
)

def poly_mean(n_features, n_outputs):
return PolyMean(degree=2, input_size=n_features, batch_shape=n_outputs)

if search_type == "random":
param_space = {
"covar_module": [
Expand Down Expand Up @@ -331,3 +252,93 @@ def model_name(self):
def _more_tags(self):
# TODO: is it really non-deterministic?
return {"multioutput": True, "non_deterministic": True}


# kernel functions for parameter search have to be outside the class so that pickle can find them
def rbf(n_features, n_outputs):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
).initialize(lengthscale=torch.ones(n_features) * 1.5)


def matern_5_2_kernel(n_features, n_outputs):
return gpytorch.kernels.MaternKernel(
nu=2.5,
ard_num_dims=n_features,
batch_shape=n_outputs,
)


def matern_3_2_kernel(n_features, n_outputs):
return gpytorch.kernels.MaternKernel(
nu=1.5,
ard_num_dims=n_features,
batch_shape=n_outputs,
)


def rq_kernel(n_features, n_outputs):
return gpytorch.kernels.RQKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)


def rbf_plus_constant(n_features, n_outputs):
return (
gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
).initialize(lengthscale=torch.ones(n_features) * 1.5)
+ gpytorch.kernels.ConstantKernel()
)


# combinations
def rbf_plus_linear(n_features, n_outputs):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
) + gpytorch.kernels.LinearKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)


def matern_5_2_plus_rq(n_features, n_outputs):
return gpytorch.kernels.MaternKernel(
nu=2.5,
ard_num_dims=n_features,
batch_shape=n_outputs,
) + gpytorch.kernels.RQKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)


def rbf_times_linear(n_features, n_outputs):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
) * gpytorch.kernels.LinearKernel(
ard_num_dims=n_features,
batch_shape=n_outputs,
)


# means
def constant_mean(n_features, n_outputs):
return gpytorch.means.ConstantMean(batch_shape=n_outputs)


def zero_mean(n_features, n_outputs):
return gpytorch.means.ZeroMean(batch_shape=n_outputs)


def linear_mean(n_features, n_outputs):
return gpytorch.means.LinearMean(input_size=n_features, batch_shape=n_outputs)


def poly_mean(n_features, n_outputs):
return PolyMean(degree=2, input_size=n_features, batch_shape=n_outputs)
101 changes: 56 additions & 45 deletions autoemulate/emulators/gaussian_process_mt.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,51 +202,6 @@ def predict(self, X, return_std=False):
def get_grid_params(self, search_type="random"):
"""Returns the grid parameters for the emulator."""

# wrapper functions for kernel initialization at fit time (to provide ard_num_dims)
# kernels
def rbf_kernel(n_features):
return gpytorch.kernels.RBFKernel(ard_num_dims=n_features).initialize(
lengthscale=torch.ones(n_features) * 1.5
)

def matern_5_2_kernel(n_features):
return gpytorch.kernels.MaternKernel(nu=2.5, ard_num_dims=n_features)

def matern_3_2_kernel(n_features):
return gpytorch.kernels.MaternKernel(nu=1.5, ard_num_dims=n_features)

def rq_kernel(n_features):
return gpytorch.kernels.RQKernel(ard_num_dims=n_features)

# combinations
def rbf_plus_linear(n_features):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features
) + gpytorch.kernels.LinearKernel(ard_num_dims=n_features)

def matern_5_2_plus_rq(n_features):
return gpytorch.kernels.MaternKernel(
nu=2.5, ard_num_dims=n_features
) + gpytorch.kernels.RQKernel(ard_num_dims=n_features)

def rbf_times_linear(n_features):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features
) * gpytorch.kernels.LinearKernel(ard_num_dims=n_features)

# means
def constant_mean(n_features):
return gpytorch.means.ConstantMean()

def zero_mean(n_features):
return gpytorch.means.ZeroMean()

def linear_mean(n_features):
return gpytorch.means.LinearMean(input_size=n_features)

def poly_mean(n_features):
return PolyMean(degree=2, input_size=n_features)

if search_type == "random":
param_space = {
"covar_module": [
Expand Down Expand Up @@ -277,3 +232,59 @@ def model_name(self):
def _more_tags(self):
# TODO: is it really non-deterministic?
return {"multioutput": True, "non_deterministic": True}


# wrapper functions for kernel initialization at fit time (to provide ard_num_dims)
# move outside class to allow pickling
def rbf_kernel(n_features):
return gpytorch.kernels.RBFKernel(ard_num_dims=n_features).initialize(
lengthscale=torch.ones(n_features) * 1.5
)


def matern_5_2_kernel(n_features):
return gpytorch.kernels.MaternKernel(nu=2.5, ard_num_dims=n_features)


def matern_3_2_kernel(n_features):
return gpytorch.kernels.MaternKernel(nu=1.5, ard_num_dims=n_features)


def rq_kernel(n_features):
return gpytorch.kernels.RQKernel(ard_num_dims=n_features)


# combinations
def rbf_plus_linear(n_features):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features
) + gpytorch.kernels.LinearKernel(ard_num_dims=n_features)


def matern_5_2_plus_rq(n_features):
return gpytorch.kernels.MaternKernel(
nu=2.5, ard_num_dims=n_features
) + gpytorch.kernels.RQKernel(ard_num_dims=n_features)


def rbf_times_linear(n_features):
return gpytorch.kernels.RBFKernel(
ard_num_dims=n_features
) * gpytorch.kernels.LinearKernel(ard_num_dims=n_features)


# means
def constant_mean(n_features):
return gpytorch.means.ConstantMean()


def zero_mean(n_features):
return gpytorch.means.ZeroMean()


def linear_mean(n_features):
return gpytorch.means.LinearMean(input_size=n_features)


def poly_mean(n_features):
return PolyMean(degree=2, input_size=n_features)
8 changes: 4 additions & 4 deletions docs/tutorials/01_start.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -813,9 +813,9 @@
"source": [
"Although we tried to chose default model parameters that work well in a wide range of scenarios, hyperparameter search will often find an emulator model with a better fit. Internally, `AutoEmulate` compares the performance of different models and hyperparameters using cross-validation on the training data, which can be computationally expensive and time-consuming for larger datasets. To speed it up, we can parallelise the process with `n_jobs`.\n",
"\n",
"For each model, we've pre-defined a search space for hyperparameters. When setting up `AutoEmulate` with `param_search=True`, we default to using random search with `param_search_iters = 20` iterations. We plan to add other hyperparameter search methods in the future. \n",
"For each model, we've pre-defined a search space for hyperparameters. When setting up `AutoEmulate` with `param_search=True`, we default to using random search with `param_search_iters = 20` iterations. This means that 20 hyperparameter combinations from the search space are sampled and evaluated. We plan to add other hyperparameter search methods in the future. \n",
"\n",
"Let's do a hyperparameter search for the Gaussian Process and Random Forest models."
"Let's do a hyperparameter search for the Support Vector Machines and Random Forest models."
]
},
{
Expand Down Expand Up @@ -1352,7 +1352,7 @@
],
"source": [
"em = AutoEmulate()\n",
"em.setup(X, y, param_search=True, param_search_type=\"random\", param_search_iters=20, models=[\"GaussianProcess\", \"RandomForest\"], n_jobs=-2) # n_jobs=-2 uses all cores but one\n",
"em.setup(X, y, param_search=True, param_search_type=\"random\", param_search_iters=10, models=[\"SupportVectorMachines\", \"RandomForest\"], n_jobs=-2) # n_jobs=-2 uses all cores but one\n",
"em.compare()"
]
},
Expand Down Expand Up @@ -1427,7 +1427,7 @@
"metadata": {},
"source": [
"**Notes**: \n",
"* Some models, such as `GaussianProcess` can be slow to run hyperparameter search on larger datasets (say n > 1500). \n",
"* Some models, such as `GaussianProcess` can be slow when conducting hyperparameter search on larger datasets (say n > 1000). \n",
"* Use the `models` argument to only run hyperparameter search on a subset of models to speed up the process.\n",
"* When possible, use `n_jobs` to parallelise the hyperparameter search. With larger datasets, we recommend setting `param_search_iters` to a lower number, such as 5, to see how long it takes to run and then increase it if necessary.\n",
"* all models can be specified with short names too, such as `rf` for `RandomForest`, `gp` for `GaussianProcess`, `svm` for `SupportVectorMachines`, etc"
Expand Down
Loading
Loading