Skip to content

Commit 0036ef0

Browse files
authored
ENH remove safe_import_context (#46)
1 parent b4ea4c7 commit 0036ef0

4 files changed

Lines changed: 52 additions & 63 deletions

File tree

benchmark_utils/__init__.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
# `benchmark_utils` is a module in which you can define code to reuse in
22
# the benchmark objective, datasets, and solvers. The folder should have the
33
# name `benchmark_utils`, and code defined inside will be importable using
4-
# the usual import syntax. To import external packages in this file, use a
5-
# `safe_import_context` named "import_ctx", as follows:
6-
7-
from benchopt.utils import safe_import_context
8-
9-
with safe_import_context() as import_ctx:
10-
import numpy as np
4+
# the usual import syntax.
5+
import numpy as np
116

127

138
def gradient_ols(X, y, beta):

datasets/simulated.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
from benchopt import BaseDataset, safe_import_context
1+
from benchopt import BaseDataset
22

3-
4-
# Protect the import with `safe_import_context()`. This allows:
5-
# - skipping import to speed up autocompletion in CLI.
6-
# - getting requirements info when all dependencies are not installed.
7-
with safe_import_context() as import_ctx:
8-
import numpy as np
3+
import numpy as np
94

105

116
# All datasets must be named `Dataset` and inherit from `BaseDataset`
@@ -26,7 +21,7 @@ class Dataset(BaseDataset):
2621
}
2722

2823
# List of packages needed to run the dataset. See the corresponding
29-
# section in objective.py
24+
# section in objective.py. This is an optional attribute.
3025
requirements = []
3126

3227
def get_data(self):

objective.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
from benchopt import BaseObjective, safe_import_context
1+
from benchopt import BaseObjective
22

3-
# Protect the import with `safe_import_context()`. This allows:
4-
# - skipping import to speed up autocompletion in CLI.
5-
# - getting requirements info when all dependencies are not installed.
6-
with safe_import_context() as import_ctx:
7-
import numpy as np
3+
import numpy as np
4+
from benchmark_utils import value_ols, gradient_ols
85

96

107
# The benchmark objective must be named `Objective` and
@@ -17,49 +14,42 @@ class Objective(BaseObjective):
1714
# URL of the main repo for this benchmark.
1815
url = "https://github.com/#ORG/#BENCHMARK_NAME"
1916

20-
# List of parameters for the objective. The benchmark will consider
21-
# the cross product for each key in the dictionary.
22-
# All parameters 'p' defined here are available as 'self.p'.
23-
# This means the OLS objective will have a parameter `self.whiten_y`.
24-
parameters = {
25-
'whiten_y': [False, True],
26-
}
27-
2817
# List of packages needed to run the benchmark.
2918
# They are installed with conda; to use pip, use 'pip:packagename'. To
3019
# install from a specific conda channel, use 'channelname:packagename'.
3120
# Packages that are not necessary to the whole benchmark but only to some
3221
# solvers or datasets should be declared in Dataset or Solver (see
3322
# simulated.py and python-gd.py).
34-
# Example syntax: requirements = ['numpy', 'pip:jax', 'pytorch:pytorch']
23+
# Example syntax: requirements = ['numpy', 'pip::jax', 'pytorch::pytorch']
3524
requirements = ["numpy"]
3625

3726
# Minimal version of benchopt required to run this benchmark.
3827
# Bump it up if the benchmark depends on a new feature of benchopt.
39-
min_benchopt_version = "1.5"
28+
min_benchopt_version = "1.7"
4029

4130
def set_data(self, X, y):
4231
# The keyword arguments of this function are the keys of the dictionary
4332
# returned by `Dataset.get_data`. This defines the benchmark's
4433
# API to pass data. This is customizable for each benchmark.
4534
self.X, self.y = X, y
4635

47-
# `set_data` can be used to preprocess the data. For instance,
48-
# if `whiten_y` is True, remove the mean of `y`.
49-
if self.whiten_y:
50-
y -= y.mean(axis=0)
51-
5236
def evaluate_result(self, beta):
5337
# The keyword arguments of this function are the keys of the
5438
# dictionary returned by `Solver.get_result`. This defines the
5539
# benchmark's API to pass solvers' result. This is customizable for
5640
# each benchmark.
57-
diff = self.y - self.X @ beta
41+
42+
# Here we can compute any metric to evaluate the quality of the
43+
# solution. We compute the value of the objective function and the
44+
# norm of the gradient.
45+
grad = gradient_ols(self.X, self.y, beta)
46+
value = value_ols(self.X, self.y, beta)
5847

5948
# This method can return many metrics in a dictionary. One of these
6049
# metrics needs to be `value` for convergence detection purposes.
6150
return dict(
62-
value=.5 * diff @ diff,
51+
value=value,
52+
grad_norm=np.linalg.vector_norm(grad),
6353
)
6454

6555
def get_one_result(self):

solvers/python-gd.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
from benchopt import BaseSolver, safe_import_context
1+
from benchopt import BaseSolver
22

3-
# Protect the import with `safe_import_context()`. This allows:
4-
# - skipping import to speed up autocompletion in CLI.
5-
# - getting requirements info when all dependencies are not installed.
6-
with safe_import_context() as import_ctx:
7-
import numpy as np
3+
import numpy as np
84

9-
# import your reusable functions here
10-
from benchmark_utils import gradient_ols
5+
# Reusable function can be imported from the benchmark_utils module, which is
6+
# dynamically installed when running the benchmark.
7+
from benchmark_utils import gradient_ols
118

129

1310
# The benchmark solvers must be named `Solver` and
@@ -17,16 +14,27 @@ class Solver(BaseSolver):
1714
# Name to select the solver in the CLI and to display the results.
1815
name = 'GD'
1916

17+
# List of packages needed to run the solver. See the corresponding
18+
# section in objective.py. This is an optional attribute.
19+
requirements = []
20+
2021
# List of parameters for the solver. The benchmark will consider
2122
# the cross product for each key in the dictionary.
2223
# All parameters 'p' defined here are available as 'self.p'.
2324
parameters = {
24-
'scale_step': [1, 1.99],
25+
'learning_rate': [0.1, 0.5],
2526
}
2627

27-
# List of packages needed to run the solver. See the corresponding
28-
# section in objective.py
29-
requirements = []
28+
# Evaluation strategy for the performance curve.
29+
# It describe when how and when the solver will be evaluated.
30+
# You can also use `iteration`, `tolerance` or `run_once`, as described in
31+
# https://benchopt.github.io/performance_curves.html
32+
# For optimization solvers, we recommend to use 'callback' which can be
33+
# used regularly to log the progress of the solver and implement a stopping
34+
# criterion.
35+
# For machine learning methods, `run_once` is usually more adapted, to
36+
# evaluate method only at the end of the training phase.
37+
sampling_strategy = 'callback'
3038

3139
def set_objective(self, X, y):
3240
# Define the information received by each solver from the objective.
@@ -36,22 +44,23 @@ def set_objective(self, X, y):
3644
# It is customizable for each benchmark.
3745
self.X, self.y = X, y
3846

39-
def run(self, n_iter):
40-
# This is the function that is called to evaluate the solver.
41-
# It runs the algorithm for a given a number of iterations `n_iter`.
42-
# You can also use a `tolerance` or a `callback`, as described in
43-
# https://benchopt.github.io/performance_curves.html
44-
45-
L = np.linalg.norm(self.X, ord=2) ** 2
46-
step_size = self.scale_step / L
47-
beta = np.zeros(self.X.shape[1])
48-
for _ in range(n_iter):
49-
beta -= step_size * gradient_ols(self.X, self.y, beta)
47+
def run(self, callback):
48+
# This is the function that is called to run the method.
49+
# When using ``sampling_strategy='callback'``, the function is provided
50+
# with a ``callback`` function that must be called regularly to
51+
# log the progress of the solver. The callback function returns
52+
# ``True`` until the solver should stop.
53+
# See https://benchopt.github.io/guide/auto_stop.html for more details.
5054

51-
self.beta = beta
55+
self.beta = np.zeros(self.X.shape[1])
56+
while callback():
57+
self.beta -= self.learning_rate * gradient_ols(
58+
self.X, self.y, self.beta
59+
)
5260

5361
def get_result(self):
54-
# Return the result from one optimization run.
62+
# Return the result of the method.
63+
#
5564
# The outputs of this function is a dictionary which defines the
5665
# keyword arguments for `Objective.evaluate_result`
5766
# This defines the benchmark's API for solvers' results.

0 commit comments

Comments
 (0)