Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

  • 698042a
  • /
  • notebooks
  • /
  • cookbooks
  • /
  • multi_level_model.ipynb
Raw File Download

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
content badge Iframe embedding
swh:1:cnt:43ab2efbebdd073122addee21623bcdc41cea6ae
directory badge Iframe embedding
swh:1:dir:df4d23563fe364875a8e5909d01ddf7c6d28f735

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
multi_level_model.ipynb
{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Cookbook: Multi Level Models\n",
        "============================\n",
        "\n",
        "A multi level model is one where one or more of the input parameters in the model components `__init__`\n",
        "constructor are Python classes, as opposed to a float or tuple.\n",
        "\n",
        "The `af.Model()` object treats these Python classes as model components, enabling the composition of models where\n",
        "model components are grouped within other Python classes, in an object oriented fashion.\n",
        "\n",
        "This enables complex models which are intiutive and extensible to be composed.\n",
        "\n",
        "This cookbook provides an overview of multi-level model composition.\n",
        "\n",
        "__Contents__\n",
        "\n",
        " - Python Class Template: The template of multi level model components written as a Python class.\n",
        " - Model Composition: How to compose a multi-level model using the `af.Model()` object.\n",
        " - Instances:  Creating an instance of a multi-level model via input parameters.\n",
        " - Why Use Multi-Level Models?: A description of the benefits of using multi-level models compared to a `Collection`.\n",
        " - Model Customization: Customizing a multi-level model (e.g. fixing parameters or linking them to one another).\n",
        " - Alternative API: Alternative API for multi-level models which may be more concise and readable for certain models.\n",
        " - Json Output (Model): Output a multi-level model in human readable text via a .json file and loading it back again."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%matplotlib inline\n",
        "from pyprojroot import here\n",
        "workspace_path = str(here())\n",
        "%cd $workspace_path\n",
        "print(f\"Working Directory has been set to `{workspace_path}`\")\n",
        "\n",
        "import json\n",
        "import os\n",
        "from os import path\n",
        "from typing import List\n",
        "\n",
        "import autofit as af"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "__Python Class Template__\n",
        "\n",
        "A multi-level model uses standard model components, which are written as a Python class with the usual format\n",
        "where the inputs of the `__init__` constructor are the model parameters."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "\n",
        "\n",
        "class Gaussian:\n",
        "    def __init__(\n",
        "        self,\n",
        "        normalization=1.0,  # <- **PyAutoFit** recognises these constructor arguments\n",
        "        sigma=5.0,  # <- are the Gaussian``s model parameters.\n",
        "    ):\n",
        "        self.normalization = normalization\n",
        "        self.sigma = sigma\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The unique aspect of a multi-level model is that a Python class can then be defined where the inputs\n",
        "of its `__init__` constructor are instances of these model components.\n",
        "\n",
        "In the example below, the Python class which will be used to demonstrate a multi-level has an input `gaussian_list`,\n",
        "which takes as input a list of instances of the `Gaussian` class above.\n",
        "\n",
        "This class will represent many individual `Gaussian`'s, which share the same `centre` but have their own unique\n",
        "`normalization` and `sigma` values."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "\n",
        "\n",
        "class MultiLevelGaussians:\n",
        "    def __init__(\n",
        "        self,\n",
        "        higher_level_centre: float = 50.0,  # The centre of all Gaussians in the multi level component.\n",
        "        gaussian_list: List[Gaussian] = None,  # Contains a list of Gaussians\n",
        "    ):\n",
        "        self.higher_level_centre = higher_level_centre\n",
        "\n",
        "        self.gaussian_list = gaussian_list\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "__Model Composition__\n",
        "\n",
        "A multi-level model is instantiated via the af.Model() command, which is passed: \n",
        "\n",
        " - `MultiLevelGaussians`: To tell it that the model component will be a `MultiLevelGaussians` object. \n",
        " - `gaussian_list`: One or more `Gaussian`'s, each of which are created as an `af.Model()` object with free parameters."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "model = af.Model(\n",
        "    MultiLevelGaussians, gaussian_list=[af.Model(Gaussian), af.Model(Gaussian)]\n",
        ")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The multi-level model consists of two `Gaussian`'s, where their centres are shared as a parameter in the higher level\n",
        "model component.\n",
        "\n",
        "Total number of parameters is N=5 (x2 `normalizations`, `x2 `sigma`'s and x1 `higher_level_centre`)."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "print(f\"Model Total Free Parameters = {model.total_free_parameters}\")"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The structure of the multi-level model, including the hierarchy of Python classes, is shown in the `model.info`."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "print(model.info)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "__Instances__\n",
        "\n",
        "Instances of a multi-level model can be created, where an input `vector` of parameters is mapped to create an instance \n",
        "of the Python class of the model.\n",
        "\n",
        "We first need to know the order of parameters in the model, so we know how to define the input `vector`. This\n",
        "information is contained in the models `paths` attribute."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "print(model.paths)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We now create an instance via a multi-level model.\n",
        "\n",
        "Its attributes are structured differently to models composed via the `Collection` object.. "
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "instance = model.instance_from_vector(vector=[1.0, 2.0, 3.0, 4.0, 5.0])\n",
        "\n",
        "print(\"Model Instance: \\n\")\n",
        "print(instance)\n",
        "\n",
        "print(\"Instance Parameters \\n\")\n",
        "print(\"Normalization (Gaussian 0) = \", instance.gaussian_list[0].normalization)\n",
        "print(\"Sigma (Gaussian 0) = \", instance.gaussian_list[0].sigma)\n",
        "print(\"Normalization (Gaussian 0) = \", instance.gaussian_list[1].normalization)\n",
        "print(\"Sigma (Gaussian 0) = \", instance.gaussian_list[1].sigma)\n",
        "print(\"Higher Level Centre= \", instance.higher_level_centre)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "__Why Use Multi Level Models?__\n",
        "\n",
        "An identical model in terms of functionality could of been created via the `Collection` object as follows:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "\n",
        "\n",
        "class GaussianCentre:\n",
        "    def __init__(\n",
        "        self,\n",
        "        centre=30.0,  # <- **PyAutoFit** recognises these constructor arguments\n",
        "        normalization=1.0,  # <- are the Gaussian``s model parameters.\n",
        "        sigma=5.0,\n",
        "    ):\n",
        "        self.centre = centre\n",
        "        self.normalization = normalization\n",
        "        self.sigma = sigma\n",
        "\n",
        "\n",
        "model = af.Collection(gaussian_0=GaussianCentre, gaussian_1=GaussianCentre)\n",
        "\n",
        "model.gaussian_0.centre = model.gaussian_1.centre"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "This raises the question of when to use a `Collection` and when to use multi-level models?\n",
        "\n",
        "The answer depends on the structure of the models you are composing and fitting.\n",
        "\n",
        "Many problems have models which have a natural multi-level structure. \n",
        "\n",
        "For example, imagine a dataset had 3 separate groups of 1D `Gaussian`'s, where each group had multiple Gaussians with \n",
        "a shared centre.\n",
        "\n",
        "This model is concise and easy to define using the multi-level API:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "group_0 = af.Model(MultiLevelGaussians, gaussian_list=3 * [Gaussian])\n",
        "\n",
        "group_1 = af.Model(MultiLevelGaussians, gaussian_list=3 * [Gaussian])\n",
        "\n",
        "group_2 = af.Model(MultiLevelGaussians, gaussian_list=3 * [Gaussian])\n",
        "\n",
        "model = af.Collection(group_0=group_0, group_1=group_1, group_2=group_2)\n",
        "\n",
        "print(model.info)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Composing the same model without the multi-level model is less concise, less readable and prone to error:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "group_0 = af.Collection(\n",
        "    gaussian_0=GaussianCentre, gaussian_1=GaussianCentre, gaussian_2=GaussianCentre\n",
        ")\n",
        "\n",
        "group_0.gaussian_0.centre = group_0.gaussian_1.centre\n",
        "group_0.gaussian_0.centre = group_0.gaussian_2.centre\n",
        "group_0.gaussian_1.centre = group_0.gaussian_2.centre\n",
        "\n",
        "group_1 = af.Collection(\n",
        "    gaussian_0=GaussianCentre, gaussian_1=GaussianCentre, gaussian_2=GaussianCentre\n",
        ")\n",
        "\n",
        "group_1.gaussian_0.centre = group_1.gaussian_1.centre\n",
        "group_1.gaussian_0.centre = group_1.gaussian_2.centre\n",
        "group_1.gaussian_1.centre = group_1.gaussian_2.centre\n",
        "\n",
        "group_2 = af.Collection(\n",
        "    gaussian_0=GaussianCentre, gaussian_1=GaussianCentre, gaussian_2=GaussianCentre\n",
        ")\n",
        "\n",
        "group_2.gaussian_0.centre = group_2.gaussian_1.centre\n",
        "group_2.gaussian_0.centre = group_2.gaussian_2.centre\n",
        "group_2.gaussian_1.centre = group_2.gaussian_2.centre\n",
        "\n",
        "model = af.Collection(group_0=group_0, group_1=group_1, group_2=group_2)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In many situations, multi-levels models are more extensible than the `Collection` API.\n",
        "\n",
        "For example, imagine we wanted to add even more 1D profiles to a group with a shared `centre`. This can easily be \n",
        "achieved using the multi-level API:\n",
        "\n",
        " multi = af.Model(\n",
        "    MultiLevelGaussians, gaussian_list=[Gaussian, Gaussian, Exponential, YourProfileHere]\n",
        " )\n",
        "\n",
        "Composing the same model using just a `Model` and `Collection` is again possible, but would be even more cumbersome,\n",
        "less readable and is not extensible.\n",
        "\n",
        "__Model Customization__\n",
        "\n",
        "To customize the higher level parameters of a multi-level the usual model API is used:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "multi = af.Model(MultiLevelGaussians, gaussian_list=[Gaussian, Gaussian])\n",
        "\n",
        "multi.higher_level_centre = af.UniformPrior(lower_limit=0.0, upper_limit=100.0)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To customize a multi-level model instantiated via lists, each model component is accessed via its index:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "multi = af.Model(MultiLevelGaussians, gaussian_list=[Gaussian, Gaussian])\n",
        "\n",
        "group_level = af.Model(MultiLevelGaussians, gaussian_list=[Gaussian, Gaussian])\n",
        "\n",
        "group_level.gaussian_list[0].normalization = group_level.gaussian_list[1].normalization"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Any combination of the API\u2019s shown above can be used for customizing this model:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "gaussian_0 = af.Model(Gaussian)\n",
        "gaussian_1 = af.Model(Gaussian)\n",
        "\n",
        "gaussian_0.normalization = gaussian_1.normalization\n",
        "\n",
        "group_level = af.Model(\n",
        "    MultiLevelGaussians, gaussian_list=[gaussian_0, gaussian_1, af.Model(Gaussian)]\n",
        ")\n",
        "\n",
        "group_level.higher_level_centre = 1.0\n",
        "group_level.gaussian_list[2].normalization = group_level.gaussian_list[1].normalization"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The `info` shows how the customization of the model has been performed:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "print(group_level.info)\n"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "__Alternative API__\n",
        "\n",
        "A multi-level model can be instantiated where each model sub-component is setup using a name (as opposed to a list).\n",
        "\n",
        "This means no list input parameter is required in the Python class of the model component, but we do need to include\n",
        "the `**kwargs` input."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "\n",
        "\n",
        "class MultiLevelGaussians:\n",
        "    def __init__(self, higher_level_centre=1.0, **kwargs):\n",
        "        self.higher_level_centre = higher_level_centre\n",
        "\n",
        "\n",
        "model = af.Model(\n",
        "    MultiLevelGaussians, gaussian_0=af.Model(Gaussian), gaussian_1=af.Model(Gaussian)\n",
        ")\n",
        "\n",
        "print(model)\n",
        "\n",
        "instance = model.instance_from_vector(vector=[1.0, 2.0, 3.0, 4.0, 5.0])\n",
        "\n",
        "print(\"Instance Parameters \\n\")\n",
        "print(\"Normalization (Gaussian 0) = \", instance.gaussian_0.normalization)\n",
        "print(\"Sigma (Gaussian 0) = \", instance.gaussian_0.sigma)\n",
        "print(\"Normalization (Gaussian 0) = \", instance.gaussian_1.normalization)\n",
        "print(\"Sigma (Gaussian 0) = \", instance.gaussian_1.sigma)\n",
        "print(\"Higher Level Centre= \", instance.higher_level_centre)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The use of Python dictionaries illustrated in previous cookbooks can also be used with multi-level models."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "\n",
        "model_dict = {\"gaussian_0\": Gaussian, \"gaussian_1\": Gaussian}\n",
        "\n",
        "model = af.Model(MultiLevelGaussians, **model_dict)\n",
        "\n",
        "print(f\"Multi-level Model Prior Count = {model.prior_count}\")\n",
        "\n",
        "instance = model.instance_from_vector(vector=[1.0, 2.0, 3.0, 4.0, 5.0])\n",
        "\n",
        "print(\"Instance Parameters \\n\")\n",
        "print(\"Normalization (Gaussian 0) = \", instance.gaussian_0.normalization)\n",
        "print(\"Sigma (Gaussian 0) = \", instance.gaussian_0.sigma)\n",
        "print(\"Normalization (Gaussian 0) = \", instance.gaussian_1.normalization)\n",
        "print(\"Sigma (Gaussian 0) = \", instance.gaussian_1.sigma)\n",
        "print(\"Higher Level Centre= \", instance.higher_level_centre)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "__JSon Outputs__\n",
        "\n",
        "A model has a `dict` attribute, which expresses all information about the model as a Python dictionary.\n",
        "\n",
        "By printing this dictionary we can therefore get a concise summary of the model."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "model = af.Model(Gaussian)\n",
        "\n",
        "print(model.dict())"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The dictionary representation printed above can be saved to hard disk as a `.json` file.\n",
        "\n",
        "This means we can save any multi-level model to hard-disk in a human readable format.\n",
        "\n",
        "Checkout the file `autofit_workspace/*/cookbooks/jsons/group_level_model.json` to see the model written as a .json."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "model_path = path.join(\"scripts\", \"cookbooks\", \"jsons\")\n",
        "\n",
        "os.makedirs(model_path, exist_ok=True)\n",
        "\n",
        "model_file = path.join(model_path, \"multi_level_model.json\")\n",
        "\n",
        "with open(model_file, \"w+\") as f:\n",
        "    json.dump(model.dict(), f, indent=4)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We can load the model from its `.json` file, meaning that one can easily save a model to hard disk and load it \n",
        "elsewhere."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "model = af.Model.from_json(file=model_file)\n",
        "\n",
        "print(model.info)"
      ],
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "__Wrap Up__\n",
        "\n",
        "This cookbook shows how to multi-level models consisting of multiple components using the `af.Model()` \n",
        "and `af.Collection()` objects.\n",
        "\n",
        "You should think carefully about whether your model fitting problem can use multi-level models, as they can make\n",
        "your model definition more concise and extensible."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [],
      "outputs": [],
      "execution_count": null
    }
  ],
  "metadata": {
    "anaconda-cloud": {},
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.6.1"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}

back to top

Software Heritage — Copyright (C) 2015–2025, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API