Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: MLE API #690

Merged
merged 85 commits into from
Dec 24, 2019
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
92c1786
MLE API
lukasheinrich Dec 16, 2019
abc8afe
MLE API
lukasheinrich Dec 16, 2019
862364e
remove example dir
lukasheinrich Dec 16, 2019
d8d1f60
remove example dir
lukasheinrich Dec 16, 2019
e5e94b8
Merge branch 'master' into errorapi
lukasheinrich Dec 16, 2019
50ae5dc
remove example dir
lukasheinrich Dec 16, 2019
751e557
remove example dir
lukasheinrich Dec 16, 2019
8c47be5
remove example dir
lukasheinrich Dec 16, 2019
0211808
adapt notebook
lukasheinrich Dec 16, 2019
bb315db
adapt notebooks
lukasheinrich Dec 16, 2019
7b87142
adapt notebooks
lukasheinrich Dec 16, 2019
bec1b14
adapt notebooks
lukasheinrich Dec 16, 2019
fe82b46
adapt notebooks
lukasheinrich Dec 16, 2019
3606d01
Merge branch 'master' into errorapi
lukasheinrich Dec 16, 2019
55f7e43
adapt notebooks
lukasheinrich Dec 16, 2019
628cf51
Try: Allow coverage drop of up to 0.2%
matthewfeickert Dec 16, 2019
4567aa8
Add codecov.yml to MANIFEST ignore
matthewfeickert Dec 16, 2019
0e10a15
coverage
lukasheinrich Dec 16, 2019
554c1a2
coverage
lukasheinrich Dec 16, 2019
8b393b0
Merge branch 'errorapi' of https://github.com/scikit-hep/pyhf into er…
lukasheinrich Dec 16, 2019
ab0ce17
Merge branch 'master' into errorapi
lukasheinrich Dec 16, 2019
0757a72
Black notebook cells and remove empty cell
matthewfeickert Dec 16, 2019
6145a6b
Update src/pyhf/infer/mle.py
lukasheinrich Dec 17, 2019
482103e
Update mle.py
lukasheinrich Dec 17, 2019
8261897
fix
lukasheinrich Dec 17, 2019
3303a6b
fix
lukasheinrich Dec 17, 2019
be33c30
add docstrings
lukasheinrich Dec 17, 2019
d263c88
add docstrings
lukasheinrich Dec 17, 2019
6b8e14a
add docstrings
lukasheinrich Dec 17, 2019
428b6ef
Merge branch 'master' into errorapi
matthewfeickert Dec 18, 2019
884ba98
Apply Black
matthewfeickert Dec 18, 2019
9973b59
Crossreference the Model objects in the docs
matthewfeickert Dec 18, 2019
a6c25b2
Add mle functions to the API docs
matthewfeickert Dec 18, 2019
ae4ace5
Fix path for pydocstyle
matthewfeickert Dec 18, 2019
1867bac
docker: entrypoint (#700)
lukasheinrich Dec 18, 2019
48eebe0
rename objective
lukasheinrich Dec 18, 2019
246df67
Merge branch 'master' into errorapi
lukasheinrich Dec 18, 2019
6651743
add docstrings (#698)
lukasheinrich Dec 18, 2019
3f35a4c
Merge branch 'master' into errorapi
lukasheinrich Dec 18, 2019
c5c6ee3
rename objective
lukasheinrich Dec 18, 2019
a38ea64
rename objective
lukasheinrich Dec 18, 2019
3cb5a96
double precision for ML backends
lukasheinrich Dec 18, 2019
fc5d10d
MLE API
lukasheinrich Dec 16, 2019
ce6a2da
MLE API
lukasheinrich Dec 16, 2019
7285f3d
remove example dir
lukasheinrich Dec 16, 2019
d1f3a7d
remove example dir
lukasheinrich Dec 16, 2019
74a52b9
remove example dir
lukasheinrich Dec 16, 2019
d55414f
remove example dir
lukasheinrich Dec 16, 2019
b0b5fe2
adapt notebook
lukasheinrich Dec 16, 2019
ba13a97
adapt notebooks
lukasheinrich Dec 16, 2019
cc70011
adapt notebooks
lukasheinrich Dec 16, 2019
dceaf82
adapt notebooks
lukasheinrich Dec 16, 2019
1d04346
adapt notebooks
lukasheinrich Dec 16, 2019
d0cebe3
adapt notebooks
lukasheinrich Dec 16, 2019
a0ce42a
coverage
lukasheinrich Dec 16, 2019
cd11db8
coverage
lukasheinrich Dec 16, 2019
98045ee
Black notebook cells and remove empty cell
matthewfeickert Dec 16, 2019
45cde07
Update src/pyhf/infer/mle.py
lukasheinrich Dec 17, 2019
f86edab
Update mle.py
lukasheinrich Dec 17, 2019
735936d
fix
lukasheinrich Dec 17, 2019
e0d8bdc
fix
lukasheinrich Dec 17, 2019
788279e
add docstrings
lukasheinrich Dec 17, 2019
d8db6ee
add docstrings
lukasheinrich Dec 17, 2019
061a0ab
add docstrings
lukasheinrich Dec 17, 2019
726f7aa
Apply Black
matthewfeickert Dec 18, 2019
a9f0e36
Crossreference the Model objects in the docs
matthewfeickert Dec 18, 2019
3b21104
Add mle functions to the API docs
matthewfeickert Dec 18, 2019
3ad1432
Fix path for pydocstyle
matthewfeickert Dec 18, 2019
644bead
rename objective
lukasheinrich Dec 18, 2019
962cdc3
rename objective
lukasheinrich Dec 18, 2019
e4b8e2f
rename objective
lukasheinrich Dec 18, 2019
d6854d4
Apply Black
matthewfeickert Dec 19, 2019
fd4c9cd
Apply Blackbook to mutlichannel coupled histo notebook
matthewfeickert Dec 19, 2019
bdbe491
Apply blackbook to pull plot notebook
matthewfeickert Dec 19, 2019
3fffe88
merge
lukasheinrich Dec 19, 2019
ff9254d
remove unsed poi_index
lukasheinrich Dec 19, 2019
6390756
Merge branch 'master' into errorapi
lukasheinrich Dec 19, 2019
d9e223a
appease giordon
lukasheinrich Dec 19, 2019
e3e782d
Merge branch 'master' into errorapi
lukasheinrich Dec 19, 2019
acd97fc
name
lukasheinrich Dec 20, 2019
f638adf
name
lukasheinrich Dec 20, 2019
4bc57f5
doctest
lukasheinrich Dec 20, 2019
2d1b880
codefactor
lukasheinrich Dec 22, 2019
6520aa8
Merge branch 'master' into errorapi
lukasheinrich Dec 22, 2019
26381ae
Merge branch 'master' into errorapi
kratsg Dec 24, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ jobs:
- name: Check docstrings
run: |
# Group 1 is related to docstrings
pydocstyle --select D1 src/pyhf/pdf.py src/pyhf/probability.py
pydocstyle --select D1 src/pyhf/pdf.py src/pyhf/probability.py src/pyhf/infer src/pyhf/optimize
- name: Test and build docs
run: |
python -m doctest README.md
Expand Down
4 changes: 3 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ Inference

hypotest
test_statistics.qmu
utils.loglambdav
mle.twice_nll
mle.fit
mle.fixed_poi_fit
utils.generate_asimov_data
utils.pvals_from_teststat
utils.pvals_from_teststat_expected
Expand Down
32 changes: 11 additions & 21 deletions docs/examples/notebooks/ImpactPlot.ipynb

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions docs/examples/notebooks/ShapeFactor.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,7 @@
"source": [
"print('initialization parameters: {}'.format(pdf.config.suggested_init()))\n",
"\n",
"unconpars = pyhf.optimizer.unconstrained_bestfit(pyhf.infer.utils.loglambdav, data, pdf,\n",
" pdf.config.suggested_init(), pdf.config.suggested_bounds())\n",
"unconpars = pyhf.infer.mle.fit(data, pdf)\n",
"print('parameters post unconstrained fit: {}'.format(unconpars))"
]
},
Expand Down Expand Up @@ -284,4 +283,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1072,8 +1072,7 @@
"nominal = pdf.config.suggested_init()\n",
"background_only = pdf.config.suggested_init()\n",
"background_only[pdf.config.poi_index] = 0.0\n",
"best_fit = pyhf.optimizer.unconstrained_bestfit(\n",
" pyhf.infer.utils.loglambdav, data, pdf, pdf.config.suggested_init(), pdf.config.suggested_bounds())"
"best_fit = pyhf.infer.mle.fit(data, pdf)"
]
},
{
Expand Down Expand Up @@ -9051,4 +9050,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
4 changes: 2 additions & 2 deletions docs/examples/notebooks/multiBinPois.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
"\n",
"print(init_pars)\n",
"\n",
"bestfit_pars = optimizer.unconstrained_bestfit(pyhf.infer.utils.loglambdav, data, pdf, init_pars, par_bounds)\n",
"bestfit_pars = pyhf.infer.mle.fit(data, pdf, init_pars, par_bounds)\n",
"bestfit_cts = pdf.expected_data(bestfit_pars, include_auxdata = False)"
]
},
Expand Down Expand Up @@ -371,4 +371,4 @@
},
"nbformat": 4,
"nbformat_minor": 1
}
}
240 changes: 123 additions & 117 deletions docs/examples/notebooks/multichannel-coupled-histo.ipynb

Large diffs are not rendered by default.

83 changes: 44 additions & 39 deletions docs/examples/notebooks/pullplot.ipynb

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions src/pyhf/infer/mle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Module for Maximum Likelihood Estimation."""
from .. import get_backend


def twice_nll(pars, data, pdf):
"""
Twice the negative Log-Likelihood.

Args:
data (`tensor`): the data
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json

Returns:
Twice the negative log likelihood.

"""
return -2 * pdf.logpdf(pars, data)


def fit(data, pdf, init_pars=None, par_bounds=None, **kwargs):
"""
Run a unconstrained maximum likelihood fit.

Args:
data (`tensor`): the data
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json
kwargs: keyword arguments passed through to the optimizer API

Returns:
see optimizer API

"""
_, opt = get_backend()
init_pars = init_pars or pdf.config.suggested_init()
par_bounds = par_bounds or pdf.config.suggested_bounds()
return opt.minimize(twice_nll, data, pdf, init_pars, par_bounds, **kwargs)


def fixed_poi_fit(
poi_val, data, pdf, init_pars=None, par_bounds=None, poi_index=None, **kwargs
):
"""
Run a maximum likelihood fit with the POI value fixzed.

Args:
data: the data
pdf (~pyhf.pdf.Model): The statistical model adhering to the schema model.json
kwargs: keyword arguments passed through to the optimizer API

Returns:
see optimizer API

"""
_, opt = get_backend()
init_pars = init_pars or pdf.config.suggested_init()
par_bounds = par_bounds or pdf.config.suggested_bounds()
return opt.minimize(
twice_nll,
data,
pdf,
init_pars,
par_bounds,
[(pdf.config.poi_index, poi_val)],
**kwargs
)
12 changes: 5 additions & 7 deletions src/pyhf/infer/test_statistics.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .. import get_backend
from .utils import loglambdav
from .mle import fixed_poi_fit, fit


def qmu(mu, data, pdf, init_pars, par_bounds):
Expand Down Expand Up @@ -30,13 +30,11 @@ def qmu(mu, data, pdf, init_pars, par_bounds):
Float: The calculated test statistic, :math:`q_{\mu}`
"""
tensorlib, optimizer = get_backend()
mubhathat = optimizer.constrained_bestfit(
loglambdav, mu, data, pdf, init_pars, par_bounds
mubhathat, fixed_val = fixed_poi_fit(
mu, data, pdf, init_pars, par_bounds, return_fval=True
)
muhatbhat = optimizer.unconstrained_bestfit(
loglambdav, data, pdf, init_pars, par_bounds
)
qmu = loglambdav(mubhathat, data, pdf) - loglambdav(muhatbhat, data, pdf)
muhatbhat, float_val = fit(data, pdf, init_pars, par_bounds, return_fval=True)
qmu = fixed_val - float_val
qmu = tensorlib.where(
muhatbhat[pdf.config.poi_index] > mu, tensorlib.astensor([0]), qmu
)
Expand Down
11 changes: 3 additions & 8 deletions src/pyhf/infer/utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
"""Utility Functions for model inference."""
from .. import get_backend


def loglambdav(pars, data, pdf):
return -2 * pdf.logpdf(pars, data)
from .mle import fixed_poi_fit


def generate_asimov_data(asimov_mu, data, pdf, init_pars, par_bounds):
_, optimizer = get_backend()
bestfit_nuisance_asimov = optimizer.constrained_bestfit(
loglambdav, asimov_mu, data, pdf, init_pars, par_bounds
)
"""Compute Asimov Dataset (expected yields at best-fit values) for a given POI value."""
bestfit_nuisance_asimov = fixed_poi_fit(asimov_mu, data, pdf, init_pars, par_bounds)
return pdf.expected_data(bestfit_nuisance_asimov)


Expand Down
2 changes: 2 additions & 0 deletions src/pyhf/optimize/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Optimizers for Tensor Functions."""

from .. import exceptions


Expand Down
46 changes: 30 additions & 16 deletions src/pyhf/optimize/autodiff.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
"""Helper Classes for use of automatic differentiation."""
import scipy
from .. import get_backend


class AutoDiffOptimizerMixin(object):
def minimize(self, objective, data, pdf, init_pars, par_bounds, fixed_vals=None):
"""Mixin Class to build optimizers that use automatic differentiation."""

def __init__(*args, **kwargs):
"""Create Mixin for autodiff-based optimizers."""
pass

def minimize(
self,
objective,
data,
pdf,
init_pars,
par_bounds,
fixed_vals=None,
return_fval=False,
):
"""
Find Function Parameters that minimize the Objective.

Returns:
bestfit parameters

"""
tensorlib, _ = get_backend()
tv, fixed_values_tensor, func, init, bounds = self.setup_minimize(
objective, data, pdf, init_pars, par_bounds, fixed_vals
Expand All @@ -12,19 +35,10 @@ def minimize(self, objective, data, pdf, init_pars, par_bounds, fixed_vals=None)
func, init, method='SLSQP', jac=True, bounds=bounds
)
nonfixed_vals = fitresult.x
return tv.stitch([fixed_values_tensor, tensorlib.astensor(nonfixed_vals)])

def unconstrained_bestfit(self, objective, data, pdf, init_pars, par_bounds):
return self.minimize(objective, data, pdf, init_pars, par_bounds)

def constrained_bestfit(
self, objective, constrained_mu, data, pdf, init_pars, par_bounds
):
return self.minimize(
objective,
data,
pdf,
init_pars,
par_bounds,
[(pdf.config.poi_index, constrained_mu)],
fitted_fval = fitresult.fun
fitted_pars = tv.stitch(
[fixed_values_tensor, tensorlib.astensor(nonfixed_vals)]
)
if return_fval:
return fitted_pars, tensorlib.astensor(fitted_fval)
return fitted_pars
54 changes: 37 additions & 17 deletions src/pyhf/optimize/opt_minuit.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""MINUIT Optimizer Backend."""

import iminuit
import logging
import numpy as np
Expand All @@ -6,7 +8,16 @@


class minuit_optimizer(object):
"""MINUIT Optimizer Backend."""

def __init__(self, verbose=False, ncall=10000, errordef=1, steps=1000):
"""
Create MINUIT Optimizer.

Args:
verbose (`bool`): print verbose output during minimization

"""
self.verbose = 0
self.ncall = ncall
self.errordef = errordef
Expand Down Expand Up @@ -45,23 +56,32 @@ def f(pars):
)
return mm

def minimize(self, objective, data, pdf, init_pars, par_bounds, fixed_vals=None):
def minimize(
self,
objective,
data,
pdf,
init_pars,
par_bounds,
fixed_vals=None,
return_fval=False,
return_uncertainties=False,
):
"""
Find Function Parameters that minimize the Objective.

Returns:
bestfit parameters

"""
mm = self._make_minuit(objective, data, pdf, init_pars, par_bounds, fixed_vals)
result = mm.migrad(ncall=self.ncall)
assert result
return np.asarray([x[1] for x in mm.values.items()])

def unconstrained_bestfit(self, objective, data, pdf, init_pars, par_bounds):
return self.minimize(objective, data, pdf, init_pars, par_bounds)

def constrained_bestfit(
self, objective, constrained_mu, data, pdf, init_pars, par_bounds
):
return self.minimize(
objective,
data,
pdf,
init_pars,
par_bounds,
[(pdf.config.poi_index, constrained_mu)],
)
if return_uncertainties:
bestfit_pars = np.asarray([(v, mm.errors[k]) for k, v in mm.values.items()])
else:
bestfit_pars = np.asarray([v for k, v in mm.values.items()])
bestfit_value = mm.fval
if return_fval:
return bestfit_pars, bestfit_value
return bestfit_pars
17 changes: 15 additions & 2 deletions src/pyhf/optimize/opt_pytorch.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
"""PyTorch Optimizer Backend."""

from .. import get_backend, default_backend
from ..tensor.common import _TensorViewer
from .autodiff import AutoDiffOptimizerMixin
import torch


class pytorch_optimizer(AutoDiffOptimizerMixin):
def __init__(self, *args, **kargs):
pass
"""PyTorch Optimizer Backend."""

def setup_minimize(
self, objective, data, pdf, init_pars, par_bounds, fixed_vals=None
):
"""
Prepare Minimization for AutoDiff-Optimizer.

Args:
objective: objective function
data: observed data
pdf: model
init_pars: initial parameters
par_bounds: parameter boundaries
fixed_vals: fixed parameter values

"""
tensorlib, _ = get_backend()
all_idx = default_backend.astensor(range(pdf.config.npars), dtype='int')
all_init = default_backend.astensor(init_pars)
Expand Down
Loading