Skip to content

Commit 7d89112

Browse files
twieckispringcoil
authored andcommitted
ENH Add Wishart with Bartlett decomposition.
DOC Add encoding information. Add WishartBartlett example. ENH Add run function to wishart example. BUG Wishart example did not use proper namespace. ENH Fix imports. Add verbosity arguments. ENH: Fixing up imports ENH: Fixing up multivariate imports
1 parent 5c213de commit 7d89112

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

pymc3/distributions/multivariate.py

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
import warnings
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
23

4+
import warnings
35
import numpy as np
4-
import theano.tensor as tt
6+
import scipy
57
import theano
8+
import theano.tensor as tt
69

710
from scipy import stats
811
from theano.tensor.nlinalg import det, matrix_inverse, trace, eigh
912

1013
from . import transforms
1114
from .distribution import Continuous, Discrete, draw_values, generate_samples
15+
from ..model import Deterministic
16+
from .continuous import ChiSquared, Normal
1217
from .special import gammaln, multigammaln
1318
from .dist_math import bound, logpow, factln
1419

15-
__all__ = ['MvNormal', 'Dirichlet', 'Multinomial', 'Wishart', 'LKJCorr']
20+
__all__ = ['MvNormal', 'Dirichlet', 'Multinomial', 'Wishart', 'WishartBartlett', 'LKJCorr']
1621

1722

1823
class MvNormal(Continuous):
@@ -310,6 +315,65 @@ def logp(self, X):
310315
n > (p - 1))
311316

312317

318+
def WishartBartlett(name, S, nu, is_cholesky=False, return_cholesky=False):
319+
"""
320+
Bartlett decomposition of the Wishart distribution. As the Wishart
321+
distribution requires the matrix to be symmetric positive semi-definite
322+
it is impossible for MCMC to ever propose acceptable matrices.
323+
324+
Instead, we can use the Barlett decomposition which samples a lower
325+
diagonal matrix. Specifically:
326+
327+
If L ~ [[sqrt(c_1), 0, ...],
328+
[z_21, sqrt(c_1), 0, ...],
329+
[z_31, z32, sqrt(c3), ...]]
330+
with c_i ~ Chi²(n-i+1) and n_ij ~ N(0, 1), then
331+
L * A * A.T * L.T ~ Wishart(L * L.T, nu)
332+
333+
See http://en.wikipedia.org/wiki/Wishart_distribution#Bartlett_decomposition
334+
for more information.
335+
336+
:Parameters:
337+
S : ndarray
338+
p x p positive definite matrix
339+
Or:
340+
p x p lower-triangular matrix that is the Cholesky factor
341+
of the covariance matrix.
342+
nu : int
343+
Degrees of freedom, > dim(S).
344+
is_cholesky : bool (default=False)
345+
Input matrix S is already Cholesky decomposed as S.T * S
346+
return_cholesky : bool (default=False)
347+
Only return the Cholesky decomposed matrix.
348+
349+
:Note:
350+
This is not a standard Distribution class but follows a similar
351+
interface. Besides the Wishart distribution, it will add RVs
352+
c and z to your model which make up the matrix.
353+
"""
354+
355+
L = S if is_cholesky else scipy.linalg.cholesky(S)
356+
357+
diag_idx = np.diag_indices_from(S)
358+
tril_idx = np.tril_indices_from(S, k=-1)
359+
n_diag = len(diag_idx[0])
360+
n_tril = len(tril_idx[0])
361+
c = T.sqrt(ChiSquared('c', nu - np.arange(2, 2+n_diag), shape=n_diag))
362+
print('Added new variable c to model diagonal of Wishart.')
363+
z = Normal('z', 0, 1, shape=n_tril)
364+
print('Added new variable z to model off-diagonals of Wishart.')
365+
# Construct A matrix
366+
A = T.zeros(S.shape, dtype=np.float32)
367+
A = T.set_subtensor(A[diag_idx], c)
368+
A = T.set_subtensor(A[tril_idx], z)
369+
370+
# L * A * A.T * L.T ~ Wishart(L*L.T, nu)
371+
if return_cholesky:
372+
return Deterministic(name, T.dot(L, A))
373+
else:
374+
return Deterministic(name, T.dot(T.dot(T.dot(L, A), A.T), L.T))
375+
376+
313377

314378
class LKJCorr(Continuous):
315379
R"""

pymc3/examples/wishart.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import pymc3 as pm
2+
import numpy as np
3+
import theano
4+
import theano.tensor as T
5+
import scipy.stats
6+
import matplotlib.pyplot as plt
7+
8+
# Covariance matrix we want to recover
9+
covariance = np.matrix([[2, .5, -.5],
10+
[.5, 1., 0.],
11+
[-.5, 0., 0.5]])
12+
13+
prec = np.linalg.inv(covariance)
14+
15+
mean = [.5, 1, .2]
16+
data = scipy.stats.multivariate_normal(mean, covariance).rvs(5000)
17+
18+
plt.scatter(data[:, 0], data[:, 1])
19+
20+
with pm.Model() as model:
21+
S = np.eye(3)
22+
nu = 5
23+
mu = pm.Normal('mu', mu=0, sd=1, shape=3)
24+
25+
# Use the transformed Wishart distribution
26+
# Under the hood this will do a Cholesky decomposition
27+
# of S and add two RVs to the sampler: c and z
28+
prec = pm.WishartBartlett('prec', S, nu)
29+
30+
# To be able to compare it to truth, convert precision to covariance
31+
cov = pm.Deterministic('cov', T.nlinalg.matrix_inverse(prec))
32+
33+
lp = pm.MvNormal('likelihood', mu=mu, tau=prec, observed=data)
34+
35+
start = pm.find_MAP()
36+
step = pm.NUTS(scaling=start)
37+
38+
39+
def run(n = 3000):
40+
if n == "short":
41+
n = 50
42+
with model:
43+
trace = pm.sample(n, step, start)
44+
45+
pm.traceplot(trace);
46+
47+
if __name__ == '__main__':
48+
run()

0 commit comments

Comments
 (0)