Skip to content

Commit dfac257

Browse files
committed
Merge branch 'main' into develop
2 parents a586439 + 0324e19 commit dfac257

File tree

7 files changed

+1040
-78
lines changed

7 files changed

+1040
-78
lines changed

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,3 @@ outputs/
142142
# inspect result logs
143143
seed_datasets_inspect_logs/
144144
seed_tasks_results/
145-
146-
# poetry lock file
147-
poetry.lock

poetry.lock

Lines changed: 768 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ dependencies = [
1919
"omegaconf>=2.3.0",
2020
"openai>=1.68.0",
2121
"ratelimit>=2.2.1",
22+
"torchvision (>=0.21.0,<0.22.0)",
23+
"torchaudio (>=2.6.0,<3.0.0)",
24+
"torch (>=2.6.0,<3.0.0)",
25+
"gpytorch (>=1.14,<2.0)",
26+
"ruff (>=0.11.4,<0.12.0)",
2227
]
2328

2429
[project.urls]
@@ -40,7 +45,7 @@ pre-commit = "^4.0.0"
4045
pytest-cov = "^3.0.0"
4146
codecov = "^2.1.13"
4247
mypy = "^1.15.0"
43-
ruff = "^0.2.2"
48+
ruff = ">=0.11.4,<0.12.0"
4449
nbqa = { version = "^1.7.0", extras = ["toolchain"] }
4550
pip-audit = "^2.7.1"
4651

src/cfg/run_cfg.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ lbo_cfg:
4949
# Train args for 'nearest_neighbor' pipeline
5050
train_frac: 0.5
5151
min_train_size: 10
52+
# Acquisition function that guides selecting the next query point.
53+
# For now, only "variance" is supported.
54+
# TODO: Add other acquisition functions.
55+
acquisition_function: "variance"
5256

5357
exp_cfg:
5458
# Set this flag to true to run test experiments during development

src/generate_capabilities.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import numpy as np
77

8-
from capability import Capability
9-
from model import Model
10-
from utils import constants
11-
from utils.capability_utils import extract_and_parse_response
12-
from utils.prompts import (
8+
from src.capability import Capability
9+
from src.model import Model
10+
from src.utils import constants
11+
from src.utils.capability_utils import extract_and_parse_response
12+
from src.utils.prompts import (
1313
CAPABILITY_GENERATION_SYSTEM_PROMPT,
1414
CAPABILITY_GENERATION_USER_PROMPT,
1515
)

src/lbo.py

Lines changed: 163 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,157 @@
1-
from typing import Any, List # noqa: D100
1+
from typing import Any, List, Tuple # noqa: D100
22

3+
import gpytorch
34
import torch
45

5-
from capability import Capability
6+
from src.capability import Capability
67

78

8-
class LBO:
9-
"""A class used to represent the Latent Bayesian Optimization (LBO) model."""
9+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
1010

11-
def __init__(self) -> None:
12-
"""Initialize the LBO parameters."""
13-
pass
1411

15-
def fit(self, X: torch.Tensor, y: torch.Tensor) -> None:
16-
"""
17-
LBO fit function.
12+
class GPModel(gpytorch.models.ExactGP): # type: ignore
13+
"""A Gaussian Process regression model using an RBF kernel."""
1814

19-
Create a mapping function from the adjusted capability representations
20-
to the capability scores.
15+
def __init__(
16+
self,
17+
train_x: torch.Tensor,
18+
train_y: torch.Tensor,
19+
likelihood: gpytorch.likelihoods.Likelihood,
20+
input_dim: int,
21+
):
22+
super().__init__(train_x.to(device), train_y.to(device), likelihood)
23+
self.mean_module = gpytorch.means.ConstantMean()
24+
self.covar_module = gpytorch.kernels.ScaleKernel(
25+
gpytorch.kernels.RBFKernel(ard_num_dims=input_dim)
26+
)
27+
self.to(device)
2128

22-
Args
23-
----
24-
X (torch.Tensor): The capability representation tensor, shape (Nc, D).
25-
y (torch.Tensor): The subject model scores corresponding
26-
to the capabilities, shape (Nc,).
29+
def forward(self, x: torch.Tensor) -> gpytorch.distributions.MultivariateNormal:
30+
"""
31+
Compute the GP prior/posterior distribution at input x.
32+
33+
Args:
34+
x (torch.Tensor): A tensor of input points at which to evaluate the GP.
35+
Shape: (n_samples, input_dim)
2736
2837
Returns
2938
-------
30-
None
39+
gpytorch.distributions.MultivariateNormal: A multivariate normal
40+
distribution representing the GP's belief over the latent function
41+
values at the input points `x`, characterized by the predicted mean
42+
and covariance.
3143
"""
32-
raise NotImplementedError
44+
x = x.to(device)
45+
mean_x = self.mean_module(x)
46+
covar_x = self.covar_module(x)
47+
return gpytorch.distributions.MultivariateNormal(mean_x, covar_x)
48+
49+
50+
class LBO:
51+
"""A class used to represent the Latent Bayesian Optimization (LBO) model.
52+
53+
The current implementation works with a finite set of candidate points for active
54+
learning. In the future we will change that to support active choice of query
55+
points.
56+
"""
3357

34-
def update(self, X: torch.Tensor, y: torch.Tensor) -> None:
58+
def __init__(
59+
self,
60+
x_train: torch.Tensor,
61+
y_train: torch.Tensor,
62+
acquisition_function: str,
63+
num_gp_train_iterations: int = 50,
64+
optimizer_lr: float = 0.1,
65+
):
66+
"""Initialize the LBO parameters."""
67+
# x_train shape is [N, d].
68+
self.input_dim = x_train.shape[1]
69+
self.x_train = x_train.clone().to(device)
70+
self.y_train = y_train.clone().to(device)
71+
self.acquisition_function = acquisition_function
72+
self.num_gp_train_iterations = num_gp_train_iterations
73+
self.optimizer_lr = optimizer_lr
74+
self.likelihood = gpytorch.likelihoods.GaussianLikelihood()
75+
self.likelihood = self.likelihood.to(device)
76+
self.model = self._train_gp()
77+
78+
def _train_gp(self) -> GPModel:
79+
model = GPModel(self.x_train, self.y_train, self.likelihood, self.input_dim)
80+
model.train()
81+
self.likelihood.train()
82+
optimizer = torch.optim.Adam(model.parameters(), lr=self.optimizer_lr)
83+
mll = gpytorch.mlls.ExactMarginalLogLikelihood(self.likelihood, model)
84+
85+
for _ in range(self.num_gp_train_iterations):
86+
optimizer.zero_grad()
87+
output = model(self.x_train)
88+
loss = -mll(output, self.y_train)
89+
loss.backward()
90+
optimizer.step()
91+
92+
model.eval()
93+
self.likelihood.eval()
94+
return model
95+
96+
def select_next_point(
97+
self, x_query: torch.Tensor
98+
) -> Tuple[torch.Tensor, torch.Tensor]:
99+
"""Select the next query point from x_query."""
100+
x_query = x_query.to(device)
101+
with torch.no_grad(), gpytorch.settings.fast_pred_var():
102+
_, st_devs = self.predict(x_query)
103+
if self.acquisition_function == "variance":
104+
idx = torch.argmax(st_devs)
105+
else:
106+
raise ValueError(
107+
f"Acquisition function: {self.acquisition_function} is unsupported."
108+
)
109+
return idx, x_query[idx]
110+
111+
def update(self, q_x: torch.Tensor, q_y: torch.Tensor) -> None:
35112
"""
36113
LBO update function.
37114
38-
Update the LBO model with new capability representation and score.
115+
Update the training set, the query set, and the LBO model.
39116
40117
Args
41118
----
42-
X (torch.Tensor): The new capability representation tensor, shape (1, D).
43-
y (torch.Tensor): The subject model score corresponding
44-
to the capability, shape (1,).
119+
q_x (torch.Tensor): The new capability representation tensor, shape (D,).
120+
q_y (torch.Tensor): The subject model score corresponding to q_x, shape
121+
(1,).
45122
46123
Returns
47124
-------
48125
None
49126
"""
50-
raise NotImplementedError
127+
q_x = q_x.to(device)
128+
q_y = (
129+
torch.tensor([q_y], device=device)
130+
if not isinstance(q_y, torch.Tensor)
131+
else q_y.to(device)
132+
)
133+
self.x_train = torch.cat([self.x_train, q_x.unsqueeze(0)], dim=0)
134+
self.y_train = torch.cat([self.y_train, q_y], dim=0)
135+
self.model = self._train_gp()
51136

52-
def predict(self, X: torch.Tensor) -> torch.Tensor:
137+
def predict(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
53138
"""
54139
LBO predict function.
55140
56141
Predict the scores for the given capability representations.
57142
58143
Args
59144
----
60-
X (torch.Tensor): The capability representation tensor with shape (Nc, D).
145+
x (torch.Tensor): The capability representation tensor with shape (Nc, D).
61146
62147
Returns
63148
-------
64-
torch.Tensor: Predicted scores for subject model.
149+
mean: Predicted mean values for input x.
150+
std: Predicted standard deviation values for input x.
65151
"""
66-
raise NotImplementedError
67-
68-
def identify_high_variance_point(self) -> torch.Tensor:
69-
"""
70-
Identify the capability representation with the highest variance.
71-
72-
Returns
73-
-------
74-
torch.Tensor: The capability representation with the highest variance.
75-
"""
76-
raise NotImplementedError
152+
x = x.to(device)
153+
vals = self.model(x)
154+
return vals.mean, vals.variance.sqrt()
77155

78156

79157
def _get_adjusted_representation(
@@ -150,9 +228,10 @@ def generate_capability_using_lbo(
150228
capability_scores: torch.Tensor,
151229
encoder: Any,
152230
pipeline_id: str = "nearest_neighbour",
231+
acquisition_function: str = "variance",
153232
decoder: Any = None,
154233
capabilities_pool: List[Capability] | None = None,
155-
) -> Capability:
234+
) -> Capability | None:
156235
"""
157236
Generate a new capability using the LBO method.
158237
@@ -164,6 +243,7 @@ def generate_capability_using_lbo(
164243
for the given capabilities.
165244
encoder (Any): The encoder model to encode the capability representation.
166245
pipeline_id (str): The pipeline identifier to determine the generation method.
246+
acquisition_function (str): The acquisition function for LBO.
167247
decoder (Any, optional): The decoder model to decode the
168248
capability representation (only for pipeline_id="discover_new").
169249
capabilities_pool (List[Capability], optional): The pool of existing
@@ -175,6 +255,7 @@ def generate_capability_using_lbo(
175255
-------
176256
Capability: The generated capability.
177257
"""
258+
capability_scores = capability_scores.to(device)
178259
# TODO:
179260
# 1. Apply the InvBO method to adjust the capabilities' representations.
180261
# capability_representations = _get_adjusted_representation(
@@ -208,15 +289,51 @@ def generate_capability_using_lbo(
208289
# generated_capability = _decode_capability(
209290
# high_variance_point, decoder
210291
# )
211-
raise NotImplementedError
292+
293+
# TODO: Part or all of the following code must be moved to run.py, especially the
294+
# loop on selecting the next capapbility. I'm commenting this out.
295+
# if pipeline_id == "nearest_neighbour":
296+
# capabilities_encoding = torch.stack(
297+
# [cap.encode(encoder) for cap in capabilities]
298+
# )
299+
# capabilities_pool_encoding = torch.stack(
300+
# [cap.encode(encoder) for cap in capabilities_pool]
301+
# )
302+
# lbo = LBO(
303+
# capabilities_encoding,
304+
# capability_scores,
305+
# acquisition_function,
306+
# )
307+
# init_pool_size = len(capabilities_pool)
308+
# for _ in range(init_pool_size):
309+
# idx, selected_capability_encoding = lbo.select_next_point(
310+
# capabilities_pool_encoding
311+
# )
312+
# # TODO: Implement and call `evaluate_capability` for the selected
313+
# capability to calculate its score.
314+
# selected_capability_score = evaluate_capability(capabilities_pool[idx])
315+
# # Remove the selected capability and its encoding.
316+
# capabilities_pool.pop(idx)
317+
# capabilities_pool_encoding = torch.cat(
318+
# [
319+
# capabilities_pool_encoding[:idx],
320+
# capabilities_pool_encoding[idx + 1 :],
321+
# ],
322+
# dim=0,
323+
# )
324+
# lbo.update(selected_capability_encoding, selected_capability_score)
325+
# else:
326+
# raise ValueError(f"Unsupported pipeline id: {pipeline_id}")
327+
328+
return None
212329

213330

214331
def generate_new_capability(
215332
capabilities: List[Capability],
216333
subject_llm_name: str,
217334
capabilities_pool: List[Capability] | None = None,
218335
**kwargs: Any,
219-
) -> Capability:
336+
) -> Capability | None:
220337
"""
221338
Generate a new capability.
222339
@@ -236,16 +353,18 @@ def generate_new_capability(
236353
if kwargs.get("lbo_run_id", 0) == 0:
237354
# Load subject LLM scores for each capability
238355
capability_scores = torch.Tensor(
239-
[cap.load_scores()[subject_llm_name] for cap in capabilities]
356+
[cap.load_scores()[subject_llm_name] for cap in capabilities], device=device
240357
)
241358
else:
242359
# Only load newly added capability's score
243360
capability_scores = torch.Tensor(
244-
[capabilities[-1].load_scores()[subject_llm_name]]
361+
[capabilities[-1].load_scores()[subject_llm_name]], device=device
245362
)
246363

247364
# TODO: Set the encoder model
248365
encoder = None
366+
if encoder is not None:
367+
encoder = encoder.to(device)
249368

250369
pipeline_id = kwargs.get("pipeline_id", "nearest_neighbour")
251370
if pipeline_id == "nearest_neighbour":

0 commit comments

Comments
 (0)