|
1
|
|
|
# copyright: hyperactive developers, MIT License (see LICENSE file) |
|
2
|
|
|
"""Extension template for experiments. |
|
3
|
|
|
|
|
4
|
|
|
Purpose of this implementation template: |
|
5
|
|
|
quick implementation of new estimators following the template |
|
6
|
|
|
NOT a concrete class to import! This is NOT a base class or concrete class! |
|
7
|
|
|
This is to be used as a "fill-in" coding template. |
|
8
|
|
|
|
|
9
|
|
|
How to use this implementation template to implement a new estimator: |
|
10
|
|
|
- make a copy of the template in a suitable location, give it a descriptive name. |
|
11
|
|
|
- work through all the "todo" comments below |
|
12
|
|
|
- fill in code for mandatory methods, and optionally for optional methods |
|
13
|
|
|
- do not write to reserved variables: _tags, _tags_dynamic |
|
14
|
|
|
- you can add more private methods, but do not override BaseEstimator's private methods |
|
15
|
|
|
an easy way to be safe is to prefix your methods with "_custom" |
|
16
|
|
|
- change docstrings for functions and the file |
|
17
|
|
|
- ensure interface compatibility by hyperactive.utils.check_estimator |
|
18
|
|
|
- once complete: use as a local library, or contribute to hyperactive via PR |
|
19
|
|
|
|
|
20
|
|
|
Mandatory methods: |
|
21
|
|
|
scoring - _score(self, params: dict) -> np.float64 |
|
22
|
|
|
parameter names - _paramnames(self) -> list[str] |
|
23
|
|
|
|
|
24
|
|
|
Testing - required for automated test framework and check_estimator usage: |
|
25
|
|
|
get default parameters for test instance(s) - get_test_params() |
|
26
|
|
|
""" |
|
27
|
|
|
# todo: write an informative docstring for the file or module, remove the above |
|
28
|
|
|
# todo: add an appropriate copyright notice for your estimator |
|
29
|
|
|
# estimators contributed should have the copyright notice at the top |
|
30
|
|
|
# estimators of your own do not need to have permissive or MIT copyright |
|
31
|
|
|
|
|
32
|
|
|
# todo: uncomment the following line, enter authors' GitHub IDs |
|
33
|
|
|
# __author__ = [authorGitHubID, anotherAuthorGitHubID] |
|
34
|
|
|
|
|
35
|
|
|
from hyperactive.base import BaseExperiment |
|
36
|
|
|
|
|
37
|
|
|
# todo: add any necessary imports here |
|
38
|
|
|
|
|
39
|
|
|
# todo: for imports of soft dependencies: |
|
40
|
|
|
# make sure to fill in the "python_dependencies" tag with the package import name |
|
41
|
|
|
# import soft dependencies only inside methods of the class, not at the top of the file |
|
42
|
|
|
|
|
43
|
|
|
|
|
44
|
|
|
class MyExperiment(BaseExperiment): |
|
45
|
|
|
"""Custom experiment. todo: write docstring. |
|
46
|
|
|
|
|
47
|
|
|
todo: describe your custom experiment here |
|
48
|
|
|
|
|
49
|
|
|
Parameters |
|
50
|
|
|
---------- |
|
51
|
|
|
parama : int |
|
52
|
|
|
descriptive explanation of parama |
|
53
|
|
|
paramb : string, optional (default='default') |
|
54
|
|
|
descriptive explanation of paramb |
|
55
|
|
|
paramc : boolean, optional (default=MyOtherEstimator(foo=42)) |
|
56
|
|
|
descriptive explanation of paramc |
|
57
|
|
|
and so on |
|
58
|
|
|
|
|
59
|
|
|
Examples |
|
60
|
|
|
-------- |
|
61
|
|
|
>>> from somehwere import MyExperiment |
|
62
|
|
|
>>> great_example(code) |
|
63
|
|
|
>>> multi_line_expressions( |
|
64
|
|
|
... require_dots_on_new_lines_so_that_expression_continues_properly |
|
65
|
|
|
... ) |
|
66
|
|
|
""" |
|
67
|
|
|
|
|
68
|
|
|
# todo: fill in tags - most tags have sensible defaults below |
|
69
|
|
|
_tags = { |
|
70
|
|
|
# tags and full specifications are available in the tag API reference |
|
71
|
|
|
# TO BE ADDED |
|
72
|
|
|
# |
|
73
|
|
|
# property tags: subtype |
|
74
|
|
|
# ---------------------- |
|
75
|
|
|
# |
|
76
|
|
|
"property:randomness": "random", |
|
77
|
|
|
# valid values: "random", "deterministic" |
|
78
|
|
|
# if "deterministic", two calls of score must result in the same value |
|
79
|
|
|
# |
|
80
|
|
|
# -------------- |
|
81
|
|
|
# packaging info |
|
82
|
|
|
# -------------- |
|
83
|
|
|
# |
|
84
|
|
|
# ownership and contribution tags |
|
85
|
|
|
# ------------------------------- |
|
86
|
|
|
# |
|
87
|
|
|
# author = author(s) of th estimator |
|
88
|
|
|
# an author is anyone with significant contribution to the code at some point |
|
89
|
|
|
"authors": ["author1", "author2"], |
|
90
|
|
|
# valid values: str or list of str, should be GitHub handles |
|
91
|
|
|
# this should follow best scientific contribution practices |
|
92
|
|
|
# scope is the code, not the methodology (method is per paper citation) |
|
93
|
|
|
# if interfacing a 3rd party estimator, ensure to give credit to the |
|
94
|
|
|
# authors of the interfaced estimator |
|
95
|
|
|
# |
|
96
|
|
|
# maintainer = current maintainer(s) of the estimator |
|
97
|
|
|
# per algorithm maintainer role, see governance document |
|
98
|
|
|
# this is an "owner" type role, with rights and maintenance duties |
|
99
|
|
|
# for 3rd party interfaces, the scope is the class only |
|
100
|
|
|
"maintainers": ["maintainer1", "maintainer2"], |
|
101
|
|
|
# valid values: str or list of str, should be GitHub handles |
|
102
|
|
|
# remove tag if maintained by package core team |
|
103
|
|
|
# |
|
104
|
|
|
# dependency tags: python version and soft dependencies |
|
105
|
|
|
# ----------------------------------------------------- |
|
106
|
|
|
# |
|
107
|
|
|
# python version requirement |
|
108
|
|
|
"python_version": None, |
|
109
|
|
|
# valid values: str, PEP 440 valid python version specifiers |
|
110
|
|
|
# raises exception at construction if local python version is incompatible |
|
111
|
|
|
# delete tag if no python version requirement |
|
112
|
|
|
# |
|
113
|
|
|
# soft dependency requirement |
|
114
|
|
|
"python_dependencies": None, |
|
115
|
|
|
# valid values: str or list of str, PEP 440 valid package version specifiers |
|
116
|
|
|
# raises exception at construction if modules at strings cannot be imported |
|
117
|
|
|
# delete tag if no soft dependency requirement |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
# todo: add any hyper-parameters and components to constructor |
|
121
|
|
|
def __init__(self, parama, paramb="default", paramc=None): |
|
122
|
|
|
# todo: write any hyper-parameters to self |
|
123
|
|
|
self.parama = parama |
|
124
|
|
|
self.paramb = paramb |
|
125
|
|
|
self.paramc = paramc |
|
126
|
|
|
# IMPORTANT: the self.params should never be overwritten or mutated from now on |
|
127
|
|
|
# for handling defaults etc, write to other attributes, e.g., self._parama |
|
128
|
|
|
|
|
129
|
|
|
# leave this as is |
|
130
|
|
|
super().__init__() |
|
131
|
|
|
|
|
132
|
|
|
# todo: optional, parameter checking logic (if applicable) should happen here |
|
133
|
|
|
# if writes derived values to self, should *not* overwrite self.parama etc |
|
134
|
|
|
# instead, write to self._parama, self._newparam (starting with _) |
|
135
|
|
|
|
|
136
|
|
|
# todo: implement this, mandatory |
|
137
|
|
|
def _paramnames(self): |
|
138
|
|
|
"""Return the parameter names of the search. |
|
139
|
|
|
|
|
140
|
|
|
Returns |
|
141
|
|
|
------- |
|
142
|
|
|
list of str |
|
143
|
|
|
The parameter names of the search parameters. |
|
144
|
|
|
""" |
|
145
|
|
|
# for every instance, this should return the correct parameter names |
|
146
|
|
|
# i.e., the maximal set of keys of the dict expected by _score |
|
147
|
|
|
return ["score_param1", "score_param2"] |
|
148
|
|
|
|
|
149
|
|
|
# todo: implement this, mandatory |
|
150
|
|
|
def _score(self, params): |
|
151
|
|
|
"""Score the parameters. |
|
152
|
|
|
|
|
153
|
|
|
Parameters |
|
154
|
|
|
---------- |
|
155
|
|
|
params : dict with string keys |
|
156
|
|
|
Parameters to score. |
|
157
|
|
|
|
|
158
|
|
|
Returns |
|
159
|
|
|
------- |
|
160
|
|
|
float |
|
161
|
|
|
The score of the parameters. |
|
162
|
|
|
dict |
|
163
|
|
|
Additional metadata about the search. |
|
164
|
|
|
""" |
|
165
|
|
|
# params is a dictionary with keys being paramnames or subset thereof |
|
166
|
|
|
# IMPORTANT: avoid side effects to params! |
|
167
|
|
|
# |
|
168
|
|
|
# the method may work if only a subste of the parameters in paramnames is passed |
|
169
|
|
|
# but this is not necessary |
|
170
|
|
|
value = 42 # must be numpy.float64 |
|
171
|
|
|
metadata = {"some": "metadata"} # can be any dict |
|
172
|
|
|
return value, metadata |
|
173
|
|
|
|
|
174
|
|
|
|
|
175
|
|
|
# todo: implement this for testing purposes! |
|
176
|
|
|
# required to run local automated unit and integration testing of estimator |
|
177
|
|
|
# method should return default parameters, so that a test instance can be created |
|
178
|
|
|
@classmethod |
|
179
|
|
|
def get_test_params(cls, parameter_set="default"): |
|
180
|
|
|
"""Return testing parameter settings for the estimator. |
|
181
|
|
|
|
|
182
|
|
|
Parameters |
|
183
|
|
|
---------- |
|
184
|
|
|
parameter_set : str, default="default" |
|
185
|
|
|
Name of the set of test parameters to return, for use in tests. If no |
|
186
|
|
|
special parameters are defined for a value, will return `"default"` set. |
|
187
|
|
|
There are currently no reserved values for this type of estimator. |
|
188
|
|
|
|
|
189
|
|
|
Returns |
|
190
|
|
|
------- |
|
191
|
|
|
params : dict or list of dict, default = {} |
|
192
|
|
|
Parameters to create testing instances of the class |
|
193
|
|
|
Each dict are parameters to construct an "interesting" test instance, i.e., |
|
194
|
|
|
`MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. |
|
195
|
|
|
`create_test_instance` uses the first (or only) dictionary in `params` |
|
196
|
|
|
""" |
|
197
|
|
|
# todo: set the testing parameters for the estimators |
|
198
|
|
|
# Testing parameters can be dictionary or list of dictionaries. |
|
199
|
|
|
# Testing parameter choice should cover internal cases well. |
|
200
|
|
|
# for "simple" extension, ignore the parameter_set argument. |
|
201
|
|
|
paramset1 = {"parama": 0, "paramb": "default", "paramc": None} |
|
202
|
|
|
paramset2 = {"parama": 1, "paramb": "foo", "paramc": 42} |
|
203
|
|
|
return [paramset1, paramset2] |
|
204
|
|
|
|
|
205
|
|
|
# this method can, if required, use: |
|
206
|
|
|
# class properties (e.g., inherited); parent class test case |
|
207
|
|
|
# imported objects such as estimators from sklearn |
|
208
|
|
|
# important: all such imports should be *inside get_test_params*, not at the top |
|
209
|
|
|
# since imports are used only at testing time |
|
210
|
|
|
# |
|
211
|
|
|
# A good parameter set should primarily satisfy two criteria, |
|
212
|
|
|
# 1. Chosen set of parameters should have a low testing time, |
|
213
|
|
|
# ideally in the magnitude of few seconds for the entire test suite. |
|
214
|
|
|
# This is vital for the cases where default values result in |
|
215
|
|
|
# "big" models which not only increases test time but also |
|
216
|
|
|
# run into the risk of test workers crashing. |
|
217
|
|
|
# 2. There should be a minimum two such parameter sets with different |
|
218
|
|
|
# sets of values to ensure a wide range of code coverage is provided. |
|
219
|
|
|
# |
|
220
|
|
|
# example 1: specify params as dictionary |
|
221
|
|
|
# any number of params can be specified |
|
222
|
|
|
# params = {"est": value0, "parama": value1, "paramb": value2} |
|
223
|
|
|
# |
|
224
|
|
|
# example 2: specify params as list of dictionary |
|
225
|
|
|
# note: Only first dictionary will be used by create_test_instance |
|
226
|
|
|
# params = [{"est": value1, "parama": value2}, |
|
227
|
|
|
# {"est": value3, "parama": value4}] |
|
228
|
|
|
# |
|
229
|
|
|
# return params |
|
230
|
|
|
|
|
231
|
|
|
@classmethod |
|
232
|
|
|
def _get_score_params(self): |
|
233
|
|
|
"""Return settings for testing the score function. Used in tests only. |
|
234
|
|
|
|
|
235
|
|
|
Returns a list, the i-th element corresponds to self.get_test_params()[i]. |
|
236
|
|
|
It should be a valid call for self.score. |
|
237
|
|
|
|
|
238
|
|
|
Returns |
|
239
|
|
|
------- |
|
240
|
|
|
list of dict |
|
241
|
|
|
The parameters to be used for scoring. |
|
242
|
|
|
""" |
|
243
|
|
|
# dict keys should be same as paramnames return |
|
244
|
|
|
# or subset, only if _score allows for subsets of parameters |
|
245
|
|
|
score_params1 = {"score_param1": "foo", "score_param2": "bar"} |
|
246
|
|
|
score_params2 = {"score_param1": "baz", "score_param2": "qux"} |
|
247
|
|
|
return [score_params1, score_params2] |
|
248
|
|
|
|