zfit / zfit-development

The developement repository for zfit with roadmaps, internal docs etc to clean up the issues
0 stars 2 forks source link

Functional API and the role of the classes #58

Open jonas-eschle opened 4 years ago

jonas-eschle commented 4 years ago

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

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.

spflueger commented 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.

jonas-eschle commented 4 years ago

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.