If parameters are defined close to usage, and strongly typed, then it's hard to cleanly search for good configurations of the parameters. Especially for fancier search strategies, you want all parameter lookups to go through a single file.
On the other hand, there's a lot of code churn until an ML pipeline is finished. And errors from typos and type violations will often only show up after hours of training. So it's also painful to try to keep a separate, loosely-typed parameter file in sync.
So far, my compromise is to:
(1) on a first pass, define all parameters as global variables at the top of the files they are used in
(2) once mostly code-complete, pull them into a separate file that tracks initial values, current values, and search ranges. Make all usages go through a lookup where the key is an enum, but the value is untyped:
def param(name: ParamName) -> Any:
return params[name].current_value
Which is not ideal. Does anyone else keep running into this problem and have a better solution?