1- from typing import Any , List # noqa: D100
1+ from typing import Any , List , Tuple # noqa: D100
22
3+ import gpytorch
34import 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
79157def _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
214331def 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