Skip to content

Commit

Permalink
Wrote a bunch of tests for the ActiveLearner select function. Fixed i…
Browse files Browse the repository at this point in the history
…ssues that showed up as tests were written
  • Loading branch information
ianran committed Dec 16, 2023
1 parent 1edeaa0 commit 04f89e7
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 19 deletions.
33 changes: 24 additions & 9 deletions src/lop/active_learning/ActiveLearner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,28 @@
# Active learning selection algorithms objective functions

import numpy as np
from copy import copy

from lop.utilities import get_pareto

import pdb

## Base Active Learning class.
#
# This class has the needed function to perform active learning from a
# gaussian proccess.
class ActiveLearner:

def __init__(self, default_to_pareto=False, always_select_best=True):
## Constructor
# @param default_to_pareto - [opt default=False] sets whether to always assume
# prefering pareto optimal choices when selecting points, if not particulary told not to
# @param alaways_select_best - [opt default=False] sets whether the select function should append the
# the top solution to the front of the solution set every time.
def __init__(self, default_to_pareto=False, always_select_best=False):
self.model = None
self.default_to_pareto=default_to_pareto
self.always_select_best = always_select_best

## set_model
# sets the model being used by the active learning framework.
# should only be called inside a model class,
def set_model(self, model):
Expand Down Expand Up @@ -73,14 +80,14 @@ def select(self, candidate_pts, num_alts, prev_selection=[], prefer_pts=None, re

# check if always_select the best value is given
if self.always_select_best and len(prev_selection) == 0:
best_idx = [self.select_best(mu, prefer_pts, prev_selection)]
best_idx = self.select_best(mu, prefer_pts, prev_selection)
sel_pts = [best_idx]
prev_selection.add(best_idx)

pref_not_sel = prefer_pts - prev_selection

if return_not_selected:
all_not_selected = set(range(len(candidate_pts))) - prev_selection
all_not_selected = (set(range(len(candidate_pts))) - prev_selection) - set(sel_pts)
not_selected = []

while len(sel_pts) < num_alts and len(all_not_selected) > 0:
Expand All @@ -100,10 +107,10 @@ def select(self, candidate_pts, num_alts, prev_selection=[], prefer_pts=None, re
not_selected.append(selected_idx)
all_not_selected.discard(selected_idx)

if len(selected_idx) != num_alts:
if len(sel_pts) != num_alts:
raise Exception("Something happened and there was not enough points to select")

return selected_idx, all_not_selected
return sel_pts, not_selected
else:
all_not_selected = None

Expand All @@ -115,14 +122,17 @@ def select(self, candidate_pts, num_alts, prev_selection=[], prefer_pts=None, re
else:
# get if the set of points not selected and not prefered if not already defined
if all_not_selected is None:
all_not_selected = set(range(len(candidate_pts))) - prev_selection
all_not_selected = (set(range(len(candidate_pts))) - prev_selection) - set(sel_pts)
# ensure that there is at least some pts left to select from
if len(all_not_selected) == 0:
raise Exception("Not enough points for select to create a full set")
selected_idx = self.select_greedy(candidate_pts, mu, data, all_not_selected)
all_not_selected.remove(all_not_selected)
all_not_selected.remove(selected_idx)

# add the selected index
sel_pts.append(selected_idx)
# end while loop
return selected_idx
return sel_pts


## get_prefered_set_of_pts
Expand All @@ -139,6 +149,8 @@ def select(self, candidate_pts, num_alts, prev_selection=[], prefer_pts=None, re
def get_prefered_set_of_pts(self, candidate_pts, prefer_pts=None):
if isinstance(prefer_pts, set):
return prefer_pts
elif isinstance(prefer_pts, list):
return set(prefer_pts)
elif prefer_pts is None and self.default_to_pareto == False:
return set(range(candidate_pts.shape[0]))
elif (prefer_pts is None and self.default_to_pareto) or (prefer_pts == 'pareto'):
Expand Down Expand Up @@ -205,3 +217,6 @@ def select_greedy_k(self, cur_selection, num_alts, data):
sel_values.append(sel_value)

return cur_selection, sel_values



67 changes: 67 additions & 0 deletions src/lop/active_learning/BestLearner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright 2023 Ian Rankin
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
# to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
# FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

# BestLearner.py
# Written Ian Rankin - February 2022
#
# A very simple algorithm that just always selects the best viable solutions
# Mostly intended for test cases

import numpy as np

from lop.active_learning import ActiveLearner

import pdb

class BestLearner(ActiveLearner):

## select_greedy
# This function greedily selects the best single data point
# Depending on the selection method, you are not forced to implement this function
# @param candidate_pts - a numpy array of points (nxk), n = number points, k = number of dimmensions
# @param mu - a numpy array of mu values outputed from predict. numpy (n)
# @param data - a user defined tuple of data (determined by the predict function of the model)
# @param indicies - a list or set of indicies in candidate points to consider.
#
# @return the index of the greedy selection.
def select_greedy(self, candidate_pts, mu, data, indicies):
indicies = list(indicies)

select_mu = mu[indicies]

return indicies[np.argmax(select_mu)]


class WorstLearner(ActiveLearner):

## select_greedy
# This function greedily selects the best single data point
# Depending on the selection method, you are not forced to implement this function
# @param candidate_pts - a numpy array of points (nxk), n = number points, k = number of dimmensions
# @param mu - a numpy array of mu values outputed from predict. numpy (n)
# @param data - a user defined tuple of data (determined by the predict function of the model)
# @param indicies - a list or set of indicies in candidate points to consider.
#
# @return the index of the greedy selection.
def select_greedy(self, candidate_pts, mu, data, indicies):
indicies = list(indicies)

select_mu = mu[indicies]

return indicies[np.argmin(select_mu)]

3 changes: 2 additions & 1 deletion src/lop/active_learning/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# init the active_learning subfolder

from .ActiveLearner import ActiveLearner
from .ActiveLearner import ActiveLearner
from .BestLearner import BestLearner, WorstLearner
5 changes: 4 additions & 1 deletion src/lop/models/Model.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ class SimplelestModel(Model):
#
# @return an array of output values (n), other output data (variance, covariance,etc)
def predict(self, X):
return X, None
if len(X.shape) > 1:
return np.sum(X, axis=1), None
else:
return X, None

def reset(self):
pass
Expand Down
Loading

0 comments on commit 04f89e7

Please sign in to comment.