Beispiel #1
0
def test_transforms(task_name):
    task = sbibm.get_task(task_name)

    observation = task.get_observation(num_observation=1)
    true_parameters = task.get_true_parameters(num_observation=1)

    transforms = task._get_transforms(
        automatic_transform_enabled=True)["parameters"]
    parameters_constrained = true_parameters
    parameters_unconstrained = transforms(true_parameters)

    lpf_1 = task._get_log_prob_fn(observation=observation,
                                  automatic_transform_enabled=False)
    log_prob_1 = lpf_1(parameters_constrained)

    lpf_2 = task._get_log_prob_fn(observation=observation,
                                  automatic_transform_enabled=True)
    # lpf_2 takes unconstrained parameters are inputs and returns
    # the log prob of the unconstrained distribution
    log_prob_2 = lpf_2(parameters_unconstrained)

    # through change of variables, we can recover the original log prob
    # ladj(x,y) -> log |dy/dx| -> ladj(untransformed, transformed)
    log_prob_3 = log_prob_2 + transforms.log_abs_det_jacobian(
        parameters_constrained, parameters_unconstrained)

    assert torch.allclose(log_prob_1, log_prob_3)
Beispiel #2
0
def test_prior(
    task_name,
    num_observation=1,
    num_samples=1000,
):
    task = sbibm.get_task(task_name)

    samples = run_prior(
        task=task,
        num_observation=num_observation,
        num_samples=num_samples,
    )

    assert len(samples) == num_samples
Beispiel #3
0
def test_log_prob_fn(task_name, jit_compile, batch_size, implementation,
                     posterior):
    """Test `get_log_prob_fn`

    Uses test cases for which the true posterior is known in closed form. Since
    `log_prob_fn` returns the unnormalized posterior log probability, it is tested
    whether the two are proportional.
    """
    task = sbibm.get_task(task_name)
    prior = task.get_prior()
    prior_dist = task.get_prior_dist()
    posterior_dist = task._get_reference_posterior(num_observation=1)

    log_prob = task._get_log_prob_fn(
        num_observation=1,
        implementation=implementation,
        jit_compile=jit_compile,
        posterior=posterior,
    )

    parameters = prior(num_samples=batch_size)

    # Test whether batching works
    if batch_size > 1:
        for b in range(batch_size):
            torch.allclose(
                log_prob(parameters)[b],
                log_prob(parameters[b, :].reshape(1, -1)))
            torch.allclose(
                posterior_dist.log_prob(parameters)[b],
                posterior_dist.log_prob(parameters[b, :].reshape(1, -1)),
            )

    # Test whether proportionality holds
    diff_ref = log_prob(parameters) - posterior_dist.log_prob(parameters)
    if not posterior:
        diff_ref += prior_dist.log_prob(parameters)
    for _ in range(10):
        parameters = prior(num_samples=batch_size)
        diff = log_prob(parameters) - posterior_dist.log_prob(parameters)
        if not posterior:
            diff += prior_dist.log_prob(parameters)
        assert torch.allclose(diff, diff_ref)
Beispiel #4
0
def test_rejection_with_proposal(
    plt,
    task_name="gaussian_linear_uniform",
    num_observation=1,
    num_samples=10000,
    prior_weight=0.1,
    multiplier_M=1.2,
    batch_size=10000,
    num_batches_without_new_max=1000,
):
    task = sbibm.get_task(task_name)

    reference_samples = task.get_reference_posterior_samples(
        num_observation=num_observation)

    proposal_dist = get_proposal(
        task=task,
        samples=reference_samples,
        prior_weight=prior_weight,
        bounded=False,
        density_estimator="flow",
        flow_model="nsf",
    )

    samples = run(
        task=task,
        num_observation=num_observation,
        num_samples=num_samples,
        batch_size=batch_size,
        num_batches_without_new_max=num_batches_without_new_max,
        multiplier_M=multiplier_M,
        proposal_dist=proposal_dist,
    )

    num_samples_plotting = 1000
    pairplot([
        samples.numpy()[:num_samples_plotting, :],
        reference_samples.numpy()[:num_samples_plotting, :],
    ])

    acc = c2st(samples, reference_samples[:num_samples, :])

    assert torch.abs(acc - 0.5) < 0.01
Beispiel #5
0
def test_log_prob_grad_fn(jit_compile, batch_size, implementation):
    """Test `get_log_prob_grad_fn`

    We are using the likleihood of the Gaussian linear using the fact that:

        ∇ wrt p of log N(p|0, 1) is -p,

    since that is the derivative of -((p-0)**2)/(2.*1.).

    We are checking against this analytical derivative.
    """
    task = sbibm.get_task("gaussian_linear", simulator_scale=1.0)
    observation = torch.zeros((10, ))
    prior = task.get_prior()

    log_prob_grad = task._get_log_prob_grad_fn(
        observation=observation,
        implementation=implementation,
        jit_compile=jit_compile,
        posterior=False,
    )

    parameters = prior(num_samples=batch_size)

    # Test whether batching works
    if batch_size > 1:
        for b in range(batch_size):
            torch.allclose(
                log_prob_grad(parameters)[b],
                log_prob_grad(parameters[b, :].reshape(1, -1)),
            )

    # Test whether gradient is correct
    grads = log_prob_grad(parameters)
    analytical_grad = -1.0 * parameters
    assert torch.allclose(grads, analytical_grad)
Beispiel #6
0
def fig_posterior(
    task_name: str,
    num_observation: int = 1,
    num_samples: int = 1000,
    prior: bool = False,
    reference: bool = True,
    true_parameter: bool = False,
    samples_path: Optional[str] = None,
    samples_tensor: Optional[torch.Tensor] = None,
    samples_name: Optional[str] = None,
    samples_color: Optional[str] = None,
    title: Optional[str] = None,
    title_dx: int = 0,
    legend: bool = True,
    seed: int = 101,
    config: Optional[str] = None,
    width: Optional[int] = None,
    height: Optional[int] = None,
    default_color: str = "#0035FD",
    colors_dict: Dict[str, Any] = {},
    interactive: bool = False,
    limits: Optional[Union[List[float], str]] = None,
    num_bins: int = 40,
    scatter_size: float = 1.0,
    **kwargs: Any,
):
    """Plots posteriors samples for given task

    Args:
        task_name: Name of the task to plot posteriors for
        num_observation: Observation number
        num_samples: Number of samples to use for plotting
        prior: Whether or not to plot prior samples
        reference: Whether or not to plot reference posterior samples
        samples_path: If specified, will load samples from disk from path
        samples_tensor: Instead of a path, samples can also be passed as torch.Tensor
        samples_name: Name for samples, defaults to "Algorithm"
        samples_color: Optional string for color of samples
        title: Title for plot
        title_dx: x-direction offset for title
        legend: Whether to plot a legend
        seed: Seed
        width: Width
        height: Height
        default_color: Default color of samples
        colors_dict: Dictionary of colors
        config: Optional string to load predefined config
        interactive: Interactive mode (experimental)
        limits: Optional limits, can also be a string, e.g., "Prior", "Ref. Posterior",
            "Algorithm" / `samples_name`, in which case respective ranges are used
        num_bins: Number of bins
        scatter_size: Scatter size

    Returns:
        Chart
    """
    # Samples to plot
    task = sbibm.get_task(task_name)
    samples = []
    labels_samples = []
    colors = {}

    samples_prior = task.get_prior()(num_samples=num_samples)
    if prior:
        sample_name = "Prior"
        samples.append(samples_prior.numpy())
        labels_samples.append(sample_name)
        if sample_name in colors_dict:
            colors[sample_name] = colors_dict[sample_name]
        else:
            colors[sample_name] = "#646464"

    if reference:
        sample_name = "Ref. Posterior"
        samples_reference = sample(
            task.get_reference_posterior_samples(
                num_observation=num_observation).numpy(),
            num_samples,
            replace=False,
            seed=seed,
        )
        samples.append(samples_reference)
        labels_samples.append(sample_name)
        if sample_name in colors_dict:
            colors[sample_name] = colors_dict[sample_name]
        else:
            colors[sample_name] = "#0a0a0a"

    if true_parameter:
        sample_name = "True parameter"
        samples.append(
            task.get_true_parameters(num_observation=num_observation).repeat(
                num_samples, 1).numpy())
        labels_samples.append(sample_name)
        if sample_name in colors_dict:
            colors[sample_name] = colors_dict[sample_name]
        else:
            colors[sample_name] = "#f92700"

    if samples_tensor is not None or samples_path is not None:
        if samples_tensor is not None:
            samples_ = samples_tensor.numpy()
        else:
            samples_ = get_ndarray_from_csv(samples_path)
        samples_algorithm = sample(samples_,
                                   num_samples,
                                   replace=False,
                                   seed=seed)
        samples.append(samples_algorithm)
        if samples_name is None:
            sample_name = "Algorithm"
        else:
            sample_name = samples_name
        labels_samples.append(sample_name)
        if samples_color is not None:
            colors[sample_name] = samples_color
        else:
            if sample_name in colors_dict:
                colors[sample_name] = colors_dict[samples_name]
            else:
                colors[sample_name] = default_color

    if len(samples) == 0:
        return None

    for s in samples:
        assert s.shape[0] == num_samples

    numbers_unicode = ["₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", "₁₀"]
    labels_dim = [
        f"θ{numbers_unicode[i+1]}" for i in range(task.dim_parameters)
    ]

    df = den.np2df(
        samples=[sample for sample in samples],
        field="sample",
        labels_samples=labels_samples,
        labels_dim=labels_dim,
    )

    style = {}
    keywords = {}

    keywords["color"] = den.colorscale(colors,
                                       shorthand="sample:N",
                                       legend=legend)
    keywords["interactive"] = interactive

    if limits is None:
        if task_name in _LIMITS_:
            limits = _LIMITS_[task_name]
        else:
            limits = [
                list(i) for i in zip(
                    samples_prior.min(dim=0)[0].tolist(),
                    samples_prior.max(dim=0)[0].tolist(),
                )
            ]
    elif type(limits) == str:
        assert limits in labels_samples
        samples_limits = torch.from_numpy(
            samples[labels_samples.index(limits)])
        limits = [
            list(i) for i in zip(
                samples_limits.min(dim=0)[0].tolist(),
                samples_limits.max(dim=0)[0].tolist(),
            )
        ]

    keywords["limits"] = limits

    keywords["num_bins"] = num_bins

    if config == "manuscript":
        style["font_family"] = "Inter"
        keywords["width"] = 100 if width is None else width
        keywords["height"] = 100 if height is None else height
        style["font_size"] = 12

    if config == "streamlit":
        size = 500 / task.dim_parameters
        keywords["width"] = size if width is None else width
        keywords["height"] = size if height is None else height
        style["font_size"] = 16

    alt.themes.enable("default")

    den.set_style(
        extra={
            "config": {
                "axisX": {
                    "domain": False,
                    "domainWidth": 0,
                    "ticks": False,
                    "tickWidth": 0,
                    "grid": False,
                },
                "axisY": {
                    "domain": False,
                    "domainWidth": 0,
                    "ticks": False,
                    "tickWidth": 0,
                    "grid": False,
                },
            }
        },
        **style,
    )

    chart = den.pairplot(
        df,
        field="sample",
        scatter_size=scatter_size,
        bar_opacity=0.4,
        **keywords,
    )

    if title is not None:
        chart = chart.properties(title={
            "text": [title],
        }).configure_title(offset=10,
                           orient="top",
                           anchor="middle",
                           dx=title_dx)

    return chart
Beispiel #7
0
import sbibm
from sbibm.algorithms.pytorch.baseline_sir import run
from sbibm.algorithms.pytorch.utils.proposal import get_proposal
from sbibm.metrics.c2st import c2st


def test_sir_with_proposal(
    plt,
    task_name="gaussian_linear_uniform",
    num_observation=1,
    num_samples=10000,
    num_simulations=10_000_000,
    batch_size=10000,
    prior_weight=0.01,
):
    task = sbibm.get_task(task_name)

    reference_samples = task.get_reference_posterior_samples(
        num_observation=num_observation)

    proposal_dist = get_proposal(
        task=task,
        samples=reference_samples,
        prior_weight=prior_weight,
        bounded=False,
        density_estimator="flow",
        flow_model="nsf",
    )

    samples = run(
        task=task,
Beispiel #8
0
def main(cfg: DictConfig) -> None:
    log = logging.getLogger(__name__)
    log.info(cfg.pretty())
    log.info(f"sbibm version: {sbibm.__version__}")
    log.info(f"Hostname: {socket.gethostname()}")
    if cfg.seed is None:
        log.info(
            "Seed not specified, generating random seed for replicability")
        cfg.seed = int(torch.randint(low=1, high=2**32 - 1, size=(1, ))[0])
        log.info(f"Random seed: {cfg.seed}")
    save_config(cfg)

    # Seeding
    torch.manual_seed(cfg.seed)
    random.seed(cfg.seed)
    np.random.seed(cfg.seed)

    # Devices
    gpu = True if cfg.device != "cpu" else False
    if gpu:
        torch.cuda.set_device(0)
        torch.set_default_tensor_type(
            "torch.cuda.FloatTensor" if gpu else "torch.FloatTensor")

    # Paths
    path_samples = "posterior_samples.csv.bz2"
    path_runtime = "runtime.csv"
    path_log_prob_true_parameters = "log_prob_true_parameters.csv"
    path_num_simulations_simulator = "num_simulations_simulator.csv"
    path_predictive_samples = "predictive_samples.csv.bz2"

    # Run
    task = sbibm.get_task(cfg.task.name)
    t0 = time.time()
    parts = cfg.algorithm.run.split(".")
    module_name = ".".join(["sbibm", "algorithms"] + parts[:-1])
    run_fn = getattr(importlib.import_module(module_name), parts[-1])
    algorithm_params = cfg.algorithm.params if "params" in cfg.algorithm else {}
    log.info("Start run")
    outputs = run_fn(
        task,
        num_observation=cfg.task.num_observation,
        num_samples=task.num_posterior_samples,
        num_simulations=cfg.task.num_simulations,
        **algorithm_params,
    )
    runtime = time.time() - t0
    log.info("Finished run")

    # Store outputs
    if type(outputs) == torch.Tensor:
        samples = outputs
        num_simulations_simulator = float("nan")
        log_prob_true_parameters = float("nan")
    elif type(outputs) == tuple and len(outputs) == 3:
        samples = outputs[0]
        num_simulations_simulator = float(outputs[1])
        log_prob_true_parameters = (float(outputs[2]) if outputs[2] is not None
                                    else float("nan"))
    else:
        raise NotImplementedError
    save_tensor_to_csv(path_samples,
                       samples,
                       columns=task.get_labels_parameters())
    save_float_to_csv(path_runtime, runtime)
    save_float_to_csv(path_num_simulations_simulator,
                      num_simulations_simulator)
    save_float_to_csv(path_log_prob_true_parameters, log_prob_true_parameters)

    # Predictive samples
    log.info("Draw posterior predictive samples")
    simulator = task.get_simulator()
    predictive_samples = []
    batch_size = 1_000
    for idx in range(int(samples.shape[0] / batch_size)):
        try:
            predictive_samples.append(
                simulator(samples[(idx * batch_size):((idx + 1) *
                                                      batch_size), :]))
        except:
            predictive_samples.append(
                float("nan") * torch.ones((batch_size, task.dim_data)))
    predictive_samples = torch.cat(predictive_samples, dim=0)
    save_tensor_to_csv(path_predictive_samples, predictive_samples,
                       task.get_labels_data())

    # Compute metrics
    if cfg.compute_metrics:
        df_metrics = compute_metrics_df(
            task_name=cfg.task.name,
            num_observation=cfg.task.num_observation,
            path_samples=path_samples,
            path_runtime=path_runtime,
            path_predictive_samples=path_predictive_samples,
            path_log_prob_true_parameters=path_log_prob_true_parameters,
            log=log,
        )
        df_metrics.to_csv("metrics.csv", index=False)
        log.info(f"Metrics:\n{df_metrics.transpose().to_string(header=False)}")
Beispiel #9
0
def compute_metrics_df(
        task_name: str,
        num_observation: int,
        path_samples: str,
        path_runtime: str,
        path_predictive_samples: str,
        path_log_prob_true_parameters: str,
        log: logging.Logger = logging.getLogger(__name__),
) -> pd.DataFrame:
    """Compute all metrics, returns dataframe
    
    Args:
        task_name: Task
        num_observation: Observation
        path_samples: Path to posterior samples
        path_runtime: Path to runtime file
        path_predictive_samples: Path to predictive samples
        path_log_prob_true_parameters: Path to NLTP
        log: Logger
    
    Returns:
        Dataframe with results
    """
    log.info(f"Compute all metrics")

    # Load task
    task = sbibm.get_task(task_name)

    # Load samples
    reference_posterior_samples = task.get_reference_posterior_samples(
        num_observation)[:task.num_posterior_samples, :]
    algorithm_posterior_samples = get_tensor_from_csv(
        path_samples)[:task.num_posterior_samples, :]
    assert reference_posterior_samples.shape[0] == task.num_posterior_samples
    assert algorithm_posterior_samples.shape[0] == task.num_posterior_samples
    log.info(
        f"Loaded {task.num_posterior_samples} samples from reference and algorithm"
    )

    # Load posterior predictive samples
    predictive_samples = get_tensor_from_csv(
        path_predictive_samples)[:task.num_posterior_samples, :]
    assert predictive_samples.shape[0] == task.num_posterior_samples

    # Load observation
    observation = task.get_observation(num_observation=num_observation)  # noqa

    # Get runtime info
    runtime_sec = torch.tensor(get_float_from_csv(path_runtime))  # noqa

    # Get log prob true parameters
    log_prob_true_parameters = torch.tensor(
        get_float_from_csv(path_log_prob_true_parameters))  # noqa

    # Names of all metrics as keys, values are calls that are passed to eval
    # NOTE: Originally, we computed a large number of metrics, as reflected in the
    # dictionary below. Ultimately, we used 10k samples and z-scoring for C2ST but
    # not for MMD. If you were to adapt this code for your own pipeline of experiments,
    # the entries for C2ST_Z, MMD and RT would probably suffice (and save compute).
    _METRICS_ = {
        #
        # 10k samples
        #
        "C2ST":
        "metrics.c2st(X=reference_posterior_samples, Y=algorithm_posterior_samples, z_score=False)",
        "C2ST_Z":
        "metrics.c2st(X=reference_posterior_samples, Y=algorithm_posterior_samples, z_score=True)",
        "MMD":
        "metrics.mmd(X=reference_posterior_samples, Y=algorithm_posterior_samples, z_score=False)",
        "MMD_Z":
        "metrics.mmd(X=reference_posterior_samples, Y=algorithm_posterior_samples, z_score=True)",
        "KSD_GAUSS":
        "metrics.ksd(task=task, num_observation=num_observation, samples=algorithm_posterior_samples, sig2=float(torch.median(torch.pdist(reference_posterior_samples))**2), log=False)",
        "MEDDIST": "metrics.median_distance(predictive_samples, observation)",
        #
        # 1K samples
        #
        "C2ST_1K":
        "metrics.c2st(X=reference_posterior_samples[:1000,:], Y=algorithm_posterior_samples[:1000,:], z_score=False)",
        "C2ST_1K_Z":
        "metrics.c2st(X=reference_posterior_samples[:1000,:], Y=algorithm_posterior_samples[:1000, :], z_score=True)",
        "MMD_1K":
        "metrics.mmd(X=reference_posterior_samples[:1000,:], Y=algorithm_posterior_samples[:1000, :], z_score=False)",
        "MMD_1K_Z":
        "metrics.mmd(X=reference_posterior_samples[:1000,:], Y=algorithm_posterior_samples[:1000, :], z_score=True)",
        "KSD_GAUSS_1K":
        "metrics.ksd(task=task, num_observation=num_observation, samples=algorithm_posterior_samples[:1000, :], sig2=float(torch.median(torch.pdist(reference_posterior_samples))**2), log=False)",
        "MEDDIST_1K":
        "metrics.median_distance(predictive_samples[:1000,:], observation)",
        #
        # Not based on samples
        #
        "NLTP": "-1. * log_prob_true_parameters",
        "RT": "runtime_sec",
    }

    import sbibm.metrics as metrics  # noqa

    metrics_dict = {}
    for metric, eval_cmd in _METRICS_.items():
        log.info(f"Computing {metric}")
        try:
            metrics_dict[metric] = eval(eval_cmd).cpu().numpy().astype(
                np.float32)
            log.info(f"{metric}: {metrics_dict[metric]}")
        except:
            metrics_dict[metric] = float("nan")

    return pd.DataFrame(metrics_dict)