-
Notifications
You must be signed in to change notification settings - Fork 119
Adds interoperability with PyTensor (and PyMC) #621
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
Merged
Merged
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
64d5882
feat: implement PyTensorOperator
cako 434796f
fix: add __pycache__ to gitignore
cako 1ed3efe
fix: docs
cako 68c415d
fix: add to docs
cako 6666804
fix: formatting
cako edc0377
fix: remove options not available to pydata_sphinx_theme
cako 0665400
fix: math formatting
cako 77a8cb2
fix: math formatting
cako 50d8caa
fix: add intersphinx
cako 59e40e6
feature: Bayesian Linear Regression
cako d68ee79
fix: change thumbnail
cako 571f6d5
fix: use == to compare string literal
cako 004bee8
fix: do not test on mac
cako df9f8e5
fix: test new versions for dev deps
cako e8a713d
fix: test new versions for dev deps
cako 8ca8ece
fix: test new versions for dev deps
cako 49b2545
fix: test new versions for dev deps
cako 24ad574
fix: test new versions for dev deps
cako 2dca9d9
fix: test new versions for dev deps
cako aa230dd
fix: test new versions for dev deps
cako 8117fca
fix: test new versions for dev deps
cako 4720b5e
fix: test new versions for dev deps
cako 038351f
lock all
cako d5c772c
fix: test new versions for dev deps
cako 48477a5
fix: test on darwin
cako 85e8253
fix: revert to old docutils
cako 23fea35
fix: passing on mac
cako 33677d6
fix: bump arviz version
cako fd6d703
fix: use old scipy
cako 88648c4
fix: remove gtraphviz (requies binary), use pytensor instead of pymc …
cako 9a83bf5
fix: improve descriptions, fix thumbnail
cako 80bf466
fix: typo
cako 956e74e
fix: improve workinng, add MAP, make chains shorter
cako 417fc24
fix: remove pytensor from yamls
cako File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ build | |
| dist | ||
| pylops.egg-info/ | ||
| .eggs/ | ||
| __pycache__ | ||
|
|
||
| # setuptools_scm generated # | ||
| pylops/version.py | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ dependencies: | |
| - autopep8 | ||
| - isort | ||
| - black | ||
| - pytensor | ||
| - pip: | ||
| - devito | ||
| - dtcwt | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ dependencies: | |
| - autopep8 | ||
| - isort | ||
| - black | ||
| - pytensor | ||
| - pip: | ||
| - devito | ||
| - dtcwt | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| r""" | ||
| Bayesian Linear Regression | ||
| ========================== | ||
|
|
||
| In the :ref:`sphx_glr_gallery_plot_linearregr.py` example, we | ||
| performed linear regression by applying a variety of solvers to the | ||
| :py:class:`pylops.LinearRegression` operator. | ||
|
|
||
| In this example, we will apply linear regression the Bayesian way. | ||
| In Bayesian inference, we are not looking for a "best" estimate | ||
| of the linear regression parameters; rather, we are looking for | ||
| all possible parameters and their associated (posterior) probability, | ||
| that is, how likely that those are the parameters that generated our data. | ||
|
|
||
| To do this, we will leverage the probabilistic programming library | ||
| `PyMC <https://www.pm.io>`_. | ||
|
|
||
| In the Bayesian formulation, we write the problem in the following manner: | ||
|
|
||
| .. math:: | ||
| y_i \sim N(x_0 + x_1 t_i, \sigma) \qquad \forall i=0,1,\ldots,N-1 | ||
|
|
||
| where :math:`x_0` is the intercept and :math:`x_1` is the gradient. | ||
| This notation means that the obtained measurements :math:`y_i` are normally distributed around | ||
| mean :math:`x_0 + x_1 t_i` with a standard deviation of :math:`\sigma`. | ||
| We can also express this problem in a matrix form, which makes it clear that we | ||
| can use a PyLops operator to describe this relationship. | ||
|
|
||
| .. math:: | ||
| \mathbf{y} \sim N(\mathbf{A} \mathbf{x}, \sigma) | ||
|
|
||
| In this example, we will combine the Bayesian power of PyMC with the linear language of | ||
| PyLops. | ||
| """ | ||
|
|
||
| import arviz as az | ||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
| import pymc as pm | ||
|
|
||
| import pylops | ||
|
|
||
| plt.close("all") | ||
| np.random.seed(10) | ||
|
|
||
| ############################################################################### | ||
| # Define the input parameters: number of samples along the t-axis (``N``), | ||
| # linear regression coefficients (``x``), and standard deviation of noise | ||
| # to be added to data (``sigma``). | ||
| N = 30 | ||
| x = np.array([1.0, 0.5]) | ||
| sigma = 0.25 | ||
|
|
||
| ############################################################################### | ||
| # Let's create the time axis and initialize the | ||
| # :py:class:`pylops.LinearRegression` operator | ||
| t = np.linspace(0, 1, N) | ||
| LRop = pylops.LinearRegression(t, dtype=t.dtype) | ||
|
|
||
| ############################################################################### | ||
| # We can then apply the operator in forward mode to compute our data points | ||
| # along the x-axis (``y``). We will also generate some random gaussian noise | ||
| # and create a noisy version of the data (``yn``). | ||
| y = LRop @ x | ||
| yn = y + np.random.normal(0, sigma, N) | ||
|
|
||
| ############################################################################### | ||
| # The deterministic solution is to solve the | ||
| # :math:`\mathbf{y} = \mathbf{A} \mathbf{x}` in a least-squares sense. | ||
| # Using PyLops, the ``/`` operator solves the iteratively (i.e., | ||
| # :py:func:`scipy.sparse.linalg.lsqr`). | ||
| xnest = LRop / yn | ||
| noise_est = np.sqrt(np.sum((yn - LRop @ xnest) ** 2) / (N - 1)) | ||
|
|
||
| ############################################################################### | ||
| # Let's plot the best fitting line for the case of noise free and noisy data | ||
| fig, ax = plt.subplots(figsize=(8, 4)) | ||
| ax.plot( | ||
| np.array([t.min(), t.max()]), | ||
| np.array([t.min(), t.max()]) * x[1] + x[0], | ||
| "k", | ||
| lw=4, | ||
| label=rf"True: $x_0$ = {x[0]:.2f}, $x_1$ = {x[1]:.2f}", | ||
| ) | ||
| ax.plot( | ||
| np.array([t.min(), t.max()]), | ||
| np.array([t.min(), t.max()]) * xnest[1] + xnest[0], | ||
| "--g", | ||
| lw=4, | ||
| label=rf"MAP Estimated: $x_0$ = {xnest[0]:.2f}, $x_1$ = {xnest[1]:.2f}", | ||
| ) | ||
| ax.scatter(t, y, c="r", s=70) | ||
| ax.scatter(t, yn, c="g", s=70) | ||
| ax.legend() | ||
| fig.tight_layout() | ||
|
|
||
| ############################################################################### | ||
| # Let's solve this problem the Bayesian way, which consists in obtaining the | ||
| # posterior probability :math:`p(\mathbf{x}\,|\,\mathbf{y})` via Bayes theorem: | ||
| # | ||
| # .. math:: | ||
| # \underbrace{p(\mathbf{x} \,|\, \mathbf{y})}_{\text{posterior}} | ||
| # \propto \overbrace{p(\mathbf{y} \,|\, \mathbf{x})}^{\text{likelihood}}\; | ||
| # \overbrace{p(\mathbf{x})}^{\text{prior}} | ||
| # | ||
| # To do so, we need to define the priors and the likelihood. | ||
| # | ||
| # Priors in Bayesian analysis can be interpreted as the probabilistic | ||
| # equivalent to regularization. Finding the maximum a posteriori (MAP) estimate | ||
| # to a least-squares problem with a Gaussian prior on the parameters is | ||
| # is equivalent to applying a Tikhonov (L2) regularization to these parameters. | ||
| # A Laplace prior is equivalent to a sparse (L1) regularization. In addition, | ||
| # the weight of the regularization is controlled by the "scale" of the | ||
| # distribution of the prior; the standard deviation (in the case of a Gaussian) | ||
| # is inversely proportional strength of the regularization. | ||
| # | ||
| # In this problem we will use weak, not very informative priors, by settings | ||
| # their "weights" to be small (high scale parameters): | ||
| # | ||
| # .. math:: | ||
| # x_0 \sim N(0, 20) | ||
| # | ||
| # x_1 \sim N(0, 20) | ||
| # | ||
| # \sigma \sim \text{HalfCauchy}(10) | ||
| # | ||
| # The (log) likelihood in Bayesian analysis is the equivalent of the cost | ||
| # function in deterministic inverse problems. In this case we have already | ||
| # seen this likelihood: | ||
| # | ||
| # .. math:: | ||
| # p(\mathbf{y}\,|\,\mathbf{x}) \sim N(\mathbf{A}\mathbf{x}, \sigma) | ||
| # | ||
|
|
||
| # Construct a PyTensor `Op` which can be used in a PyMC model. | ||
| pytensor_lrop = pylops.PyTensorOperator(LRop) | ||
| dims = pytensor_lrop.dims # Inherits dims, dimsd and shape from LRop | ||
|
|
||
| # Construct the PyMC model | ||
| with pm.Model() as model: | ||
| y_data = pm.Data("y_data", yn) | ||
|
|
||
| # Define priors | ||
| sp = pm.HalfCauchy("σ", beta=10) | ||
| xp = pm.Normal("x", 0, sigma=20, shape=dims) | ||
| mu = pm.Deterministic("mu", pytensor_lrop(xp)) | ||
|
|
||
| # Define likelihood | ||
| likelihood = pm.Normal("y", mu=mu, sigma=sp, observed=y_data) | ||
|
|
||
| # Inference! | ||
| idata = pm.sample(2000, tune=1000, chains=2) | ||
|
|
||
| ############################################################################### | ||
| # The plot below is known as the "trace" plot. The left column displays the | ||
| # posterior distributions of all latent variables in the model. The top-left | ||
| # plot has multiple colored posteriors, one for each parameter of the latent | ||
| # vector :math:`\mathbf{x}`. The bottom left plot displays the posterior of the | ||
| # estimated noise :math:`\sigma`. | ||
| # | ||
| # In these plots there are multiple distributions of the same color and | ||
| # multiple line styles. Each of these represents a "chain". A chain is a single | ||
| # run of a Monte Carlo algorithm. Generally, Monte Carlo methods run various | ||
| # chains to ensure that all regions of the posterior distribution are sampled. | ||
| # These chains are shown on the right hand plots. | ||
|
|
||
| axes = az.plot_trace(idata, figsize=(10, 7), var_names=["~mu"]) | ||
| axes[0, 0].axvline(x[0], label="True Intercept", lw=2, color="k") | ||
| axes[0, 0].axvline(xnest[0], label="Intercept MAP", lw=2, color="C0", ls="--") | ||
| axes[0, 0].axvline(x[1], label="True Slope", lw=2, color="darkgray") | ||
| axes[0, 0].axvline(xnest[1], label="Slope MAP", lw=2, color="C1", ls="--") | ||
| axes[0, 1].axhline(x[0], label="True Intercept", lw=2, color="k") | ||
| axes[0, 1].axhline(xnest[0], label="Intercept MAP", lw=2, color="C0", ls="--") | ||
| axes[0, 1].axhline(x[1], label="True Slope", lw=2, color="darkgray") | ||
| axes[0, 1].axhline(xnest[1], label="Slope MAP", lw=2, color="C1", ls="--") | ||
| axes[1, 0].axvline(sigma, label="True Sigma", lw=2, color="k") | ||
| axes[1, 0].axvline(noise_est, label="Sigma MAP", lw=2, color="C0", ls="--") | ||
| axes[1, 1].axhline(sigma, label="True Sigma", lw=2, color="k") | ||
| axes[1, 1].axhline(noise_est, label="Sigma MAP", lw=2, color="C0", ls="--") | ||
| for ax in axes.ravel(): | ||
| ax.legend() | ||
| ax.get_figure().tight_layout() | ||
|
|
||
| ################################################################################ | ||
| # With this model, we can obtain an uncertainty measurement via the High Density | ||
| # Interval. To do that, we need to sample the "preditive posterior", that is, | ||
| # the posterior distribution of the data, given the model. What this does is | ||
| # sample the latent vetors from their posteriors (above), and use the model | ||
| # to construct realizations of the data given these realizations. They represent | ||
| # what the model thinks the data should look like, given everything it has | ||
| # already seen. | ||
|
|
||
| with model: | ||
| pm.sample_posterior_predictive(idata, extend_inferencedata=True) | ||
|
|
||
| ############################################################################### | ||
| # sphinx_gallery_thumbnail_number = 3 | ||
| fig, ax = plt.subplots(figsize=(8, 4)) | ||
| az.plot_hdi( | ||
| t, | ||
| idata.posterior_predictive["y"], | ||
| fill_kwargs={"label": "95% HDI"}, | ||
| hdi_prob=0.95, | ||
| ax=ax, | ||
| ) | ||
| ax.plot( | ||
| np.array([t.min(), t.max()]), | ||
| np.array([t.min(), t.max()]) * x[1] + x[0], | ||
| "k", | ||
| lw=4, | ||
| label=rf"True: $x_0$ = {x[0]:.2f}, $x_1$ = {x[1]:.2f}", | ||
| ) | ||
| ax.plot( | ||
| np.array([t.min(), t.max()]), | ||
| np.array([t.min(), t.max()]) * xnest[1] + xnest[0], | ||
| "--g", | ||
| lw=4, | ||
| label=rf"MAP Estimated: $x_0$ = {xnest[0]:.2f}, $x_1$ = {xnest[1]:.2f}", | ||
| ) | ||
| ax.scatter(t, y, c="r", s=70) | ||
| ax.scatter(t, yn, c="g", s=70) | ||
| ax.legend() | ||
| fig.tight_layout() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.