|
| 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] |
0 commit comments