This tutorial shows how to use SBI in ROOT. As reference distribution we choose a simple uniform distribution. The target distribution is chosen to be gaussian with different mean values. The classifier is trained to discriminate between the reference and target distribution. We see how the neural networks generalize to unknown mean values.
We compare the approach of using the likelihood ratio trick to morphing.
A short recap: The idea of SBI is to fit a surrogate model to the data, in order to really learn the likelihood function instead of calculating it. Therefore, a classifier is trained to discriminate between samples from a target distribution (here the Gaussian) $$x\sim p(x|\theta)$$ and a reference distribution (here the Uniform) $$x\sim p_{ref}(x|\theta)$$.
The output of the classifier $$\hat{s}(\theta)$$ is a monotonic function of the likelihood ration and can be turned into an estimate of the likelihood ratio via $$\hat{r}(\theta)=\frac{1-\hat{s}(\theta)}{\hat{s}(\theta)}.$$ This is called the likelihood ratio trick.
In the end we compare the negative logarithmic likelihoods of the learned, morphed and analytical likelihood with minuit and as a plot.
import numpy as np
import ROOT
from sklearn.neural_network import MLPClassifier
n_samples = 10000
ROOT.RooMsgService.instance().setGlobalKillBelow(ROOT.RooFit.WARNING)
def morphing(setting, workspace):
grid = ROOT.RooMomentMorphFuncND.Grid(ROOT.RooBinning(4, 0.0, 4.0))
x_var.setBins(50)
n_grid = 5
for i in range(n_grid):
mu_help = ROOT.RooRealVar(f"mu{i}", f"mu{i}", i)
help = ROOT.RooGaussian(f"g{i}", f"g{i}", x_var, mu_help, sigma)
workspace.Import(help, Silence=True)
hist = workspace[f"g{i}"].generateBinned([x_var], n_samples)
for i_bin in range(hist.numEntries()):
hist.add(hist.get(i_bin), 1.0)
workspace.Import(ROOT.RooHistPdf(f"histpdf{i}", f"histpdf{i}", [x_var], hist, 1), Silence=True)
grid.addPdf(workspace[f"histpdf{i}"], i)
morph_func = ROOT.RooMomentMorphFuncND("morph_func", "morph_func", [mu_var], [x_var], grid, setting)
morph = ROOT.RooWrapperPdf("morph", "morph", morph_func, True)
workspace.Import(morph, Silence=True)
class SBI:
def __init__(self, workspace):
self.classifier = MLPClassifier(hidden_layer_sizes=(20, 20), max_iter=1000, random_state=42)
self.data_model = None
self.data_ref = None
self.X_train = None
self.y_train = None
self.workspace = workspace
def model_data(self, model, x, mu, n_samples):
ws = self.workspace
data_test_model = []
samples_gaussian = ws[model].generate([ws[x], ws[mu]], n_samples).to_numpy()
self._training_mus = samples_gaussian[mu]
data_test_model.extend(samples_gaussian[x])
self.data_model = np.array(data_test_model).reshape(-1, 1)
def reference_data(self, model, x, n_samples):
ws = self.workspace
samples_uniform = ws[model].generate(ws[x], n_samples)
data_reference_model = np.array(
[samples_uniform.get(i).getRealValue("x") for i in range(samples_uniform.numEntries())]
)
self.data_ref = data_reference_model.reshape(-1, 1)
def preprocessing(self):
thetas_model = self._training_mus.reshape(-1, 1)
thetas_reference = self._training_mus.reshape(-1, 1)
thetas = np.concatenate((thetas_model, thetas_reference), axis=0)
X = np.concatenate([self.data_model, self.data_ref])
self.y_train = np.concatenate([np.ones(len(self.data_model)), np.zeros(len(self.data_ref))])
self.X_train = np.concatenate([X, thetas], axis=1)
def train_classifier(self):
self.classifier.fit(self.X_train, self.y_train)
n_samples_train = n_samples * 5
def build_ws(mu_observed, sigma):
ws = ROOT.RooWorkspace()
ws.factory(f"Gaussian::gauss(x[-5,15], mu[0,4], {sigma})")
ws.factory("Uniform::uniform(x)")
ws["mu"].setVal(mu_observed)
ws.Print("v")
obs_data = ws["gauss"].generate(ws["x"], 1000)
obs_data.SetName("obs_data")
ws.Import(obs_data, Silence=True)
return ws
mu_observed = 2.5
sigma = 1.5
workspace = build_ws(mu_observed, sigma)
x_var = workspace["x"]
mu_var = workspace["mu"]
gauss = workspace["gauss"]
uniform = workspace["uniform"]
obs_data = workspace["obs_data"]
model = SBI(workspace)
model.model_data("gauss", "x", "mu", n_samples_train)
model.reference_data("uniform", "x", n_samples_train)
model.preprocessing()
model.train_classifier()
sbi_model = model
def learned_likelihood_ratio(x, mu):
n = max(len(x), len(mu))
X = np.zeros((n, 2))
X[:, 0] = x
X[:, 1] = mu
prob = sbi_model.classifier.predict_proba(X)[:, 1]
return prob / (1 - prob)
llhr_learned = ROOT.RooFit.bindFunction("MyBinFunc", learned_likelihood_ratio, x_var, mu_var)
llhr_calc = ROOT.RooFormulaVar("llhr_calc", "x[0] / x[1]", [gauss, uniform])
nll_gauss = gauss.createNLL(obs_data)
ROOT.SetOwnership(nll_gauss, True)
pdf_learned = ROOT.RooWrapperPdf("learned_pdf", "learned_pdf", llhr_learned, True)
nllr_learned = pdf_learned.createNLL(obs_data)
ROOT.SetOwnership(nllr_learned, True)
morphing(ROOT.RooMomentMorphFuncND.Linear, workspace)
nll_morph = workspace["morph"].createNLL(obs_data)
ROOT.SetOwnership(nll_morph, True)
frame1 = mu_var.frame(Title="NLL of SBI vs. Morphing;mu;NLL", Range=(2.2, 2.8))
nllr_learned.plotOn(frame1, LineColor="kP6Blue", ShiftToZero=True, Name="learned")
nll_gauss.plotOn(frame1, LineColor="kP6Yellow", ShiftToZero=True, Name="gauss")
ROOT.RooAbsReal.setEvalErrorLoggingMode("Ignore")
nll_morph.plotOn(frame1, LineColor="kP6Red", ShiftToZero=True, Name="morphed")
ROOT.RooAbsReal.setEvalErrorLoggingMode("PrintErrors")
frame2 = x_var.frame(Title="Likelihood ratio r(x|#mu=2.5);x;p_{gauss}/p_{uniform}")
llhr_learned.plotOn(frame2, LineColor="kP6Blue", Name="learned_ratio")
llhr_calc.plotOn(frame2, LineColor="kP6Yellow", Name="exact")
single_canvas = True
c = ROOT.TCanvas("", "", 1200 if single_canvas else 600, 600)
if single_canvas:
c.Divide(2)
c.cd(1)
ROOT.gPad.SetLeftMargin(0.15)
frame1.GetYaxis().SetTitleOffset(1.8)
frame1.Draw()
legend1 = ROOT.TLegend(0.43, 0.63, 0.8, 0.87)
legend1.SetFillColor("kWhite")
legend1.SetLineColor("kWhite")
legend1.SetTextSize(0.04)
legend1.AddEntry("learned", "learned (SBI)", "L")
legend1.AddEntry("gauss", "true NLL", "L")
legend1.AddEntry("morphed", "moment morphing", "L")
legend1.Draw()
if single_canvas:
c.cd(2)
ROOT.gPad.SetLeftMargin(0.15)
frame2.GetYaxis().SetTitleOffset(1.8)
else:
c.SaveAs("rf615_plot_1.png")
c = ROOT.TCanvas("", "", 600, 600)
frame2.Draw()
legend2 = ROOT.TLegend(0.53, 0.73, 0.87, 0.87)
legend2.SetFillColor("kWhite")
legend2.SetLineColor("kWhite")
legend2.SetTextSize(0.04)
legend2.AddEntry("learned_ratio", "learned (SBI)", "L")
legend2.AddEntry("exact", "true ratio", "L")
legend2.Draw()
if not single_canvas:
c.SaveAs("rf615_plot_2.png")
for nll in [nll_gauss, nllr_learned, nll_morph]:
minimizer = ROOT.RooMinimizer(nll)
minimizer.setErrorLevel(0.5)
minimizer.setPrintLevel(-1)
minimizer.minimize("Minuit2")
result = minimizer.save()
ROOT.SetOwnership(result, True)
result.Print()
del nll_morph
del nllr_learned
del nll_gauss
del workspace