Open jonas-eschle opened 4 years ago
From the pwa project standpoint: the main functionality of zfit would be the estimators and optimizers, as they are model independent. The models itself are usually quite special/complex.
Currently I'm working with these basic interfaces:
class Function(ABC):
@abstractmethod
def __call__(self, dataset: dict) -> list:
@property
@abstractmethod
def parameters(self) -> dict:
@abstractmethod
def update_parameters(self, new_parameters: dict) -> None:
class Estimator[aka. Loss](ABC):
@abstractmethod
def __call__(self) -> float:
@property
@abstractmethod
def parameters(self) -> dict:
@abstractmethod
def update_parameters(self, new_parameters: dict) -> None:
class Optimizer(ABC):
@abstractmethod
def optimize(self, estimator: Estimator, initial_parameters: dict) -> dict:
The crucial interface would be optimize
. Here I found its very convenient to simply hand all free fit parameters (initial_parameters
) to the optimizer in a list. They are completely decoupled from the model itself. Which follows the functional approach. Note that each Function and Estimator defines its parameters itself, so they are bound to them.
The parameters that can be passed should, in general, only be the parameters directly for this function. It is not possible to pass parameters through composite models for example, as i.e. a sum of two Gaussians may has well defined "fracs" but ill defined "mu" and "sigma" parameters (pdf1 or pdf2?). In principle, for specific cases, a naming scheme could be introduced ("pdf1/mu"), which is prone to fail very fast: The uppermost pdf would need to know the exact structure of itself in order to encode it into the name, something that is clearly an anti-goal.
In the PWA case, the models are directly constructed from a "recipe" which formulates the mathematical intention. All occurring parameters are uniquely identified by their name. The whole is built up and during this build phase all parameters a managed correctly. Once this whole model is constructed, it is wrapped by this function interface. This avoids having to pass down the parameters into smaller parts of the functions.
Sorry, I forgot to reply on this: That looks actually great! It is very similar to zfit, I think it misses a few things in consistency, but the basic concept is basically the same. This could also give things like simultaneous fits for free.
They are completely decoupled from the model itself. Which follows the functional approach.
For example this: I think that's not quite true. Parameters still have a state (in AmpliTF), they are constructed by a function. I fully agree on handing them to the minimizer (zfit minimizer API: Minimizer.minimize(loss, params)
, but I think we miss/misunderstand still a few things here.
The models itself are usually quite special/complex
That I agree, however, this is absolutely not a problem, as zfit is built to incorporate any complexity of model. A model does not have to be constructed by smaller models (but can). (so wrapping a model with thousands of lines of code made by AmpliTF and 50-100 free parameters is no problem in zfit and literally a few lines of code)
Anyway, I guess I mentioned it already enough: Great work on the design ! It would be good to have a throughout chat soon to fine tune things.
In general, a functional API is preferred with TensorFlow. However, due to the complexity of certain fits, purely functional can fail. To have more explicitly specify parameters in a function would make it more functional and more flexible to switch between data/parameter
Philosophy and guideline
Every object should be self-contained and not need additional arguments when methods are called. E.g.
pdf
should return the value of the pdf, the variables that it is normalized over should be set in the PDF. On the other hand, it would be useful if arguments can be given, e.g. a different normalization or similar.The API is functional while the class remembers the "default values". Anything deviating from the "default values" is the own responsibility to get it right.
Limitations of functional API
The parameters that can be passed should, in general, only be the parameters directly for this function. It is not possible to pass parameters through composite models for example, as i.e. a sum of two Gaussians may has well defined "fracs" but ill defined "mu" and "sigma" parameters (pdf1 or pdf2?). In principle, for specific cases, a naming scheme could be introduced (
"pdf1/mu"
), which is prone to fail very fast: The uppermost pdf would need to know the exact structure of itself in order to encode it into the name, something that is clearly an anti-goal.API specs
Generally, we need
vars
This would also handle the conditional case with one ambiguity: single events. If a variable is given and the normalization range is specified over it, the average of the points evaluated will be used. If a
vars
is given as a dataset (multiple points), it will be used as conditional. We need to distinguish between a single number parameter that is supposed to broadcast and a single datapoint, which is supposed to be taken as a single conditional point and should not broadcast.