Skip to content

Commit a081ced

Browse files
committed
variational lower bound
1 parent fbecc1b commit a081ced

File tree

2 files changed

+87
-6
lines changed

2 files changed

+87
-6
lines changed

bayesml/gaussianmixture/_gaussianmixture.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from scipy.stats import multivariate_normal as ss_multivariate_normal
88
from scipy.stats import wishart as ss_wishart
99
from scipy.stats import multivariate_t as ss_multivariate_t
10+
from scipy.stats import dirichlet as ss_dirichlet
11+
from scipy.special import gammaln, digamma, xlogy
1012
import matplotlib.pyplot as plt
1113

1214
from .. import base
@@ -450,7 +452,7 @@ class LearnModel(base.Posterior,base.PredictiveMixin):
450452
the inverse matrices of hn_w_mats
451453
r_vecs : numpy.ndarray
452454
vectors of real numbers. The sum of its elenemts is 1.
453-
Ns : numpy.ndarray
455+
ns : numpy.ndarray
454456
positive real numbers
455457
s_mats : numpy.ndarray
456458
positive difinite symmetric matrices
@@ -486,6 +488,9 @@ def __init__(
486488
self.h0_w_mats = np.tile(np.identity(self.degree),[self.num_classes,1,1])
487489
self.h0_w_mats_inv = np.linalg.inv(self.h0_w_mats)
488490

491+
self.LN_C_H0_ALPHA = 0.0
492+
self.LN_B_H0_W_NUS = np.empty(self.num_classes)
493+
489494
# hn_params
490495
self.hn_alpha_vec = np.empty([self.num_classes])
491496
self.hn_m_vecs = np.empty([self.num_classes,self.degree])
@@ -494,6 +499,15 @@ def __init__(
494499
self.hn_w_mats = np.empty([self.num_classes,self.degree,self.degree])
495500
self.hn_w_mats_inv = np.empty([self.num_classes,self.degree,self.degree])
496501

502+
# statistics
503+
self.r_vecs = None
504+
self.x_bar_vecs = np.empty([self.num_classes,self.degree])
505+
self.ns = np.empty(self.num_classes)
506+
self.s_mats = np.empty([self.num_classes,self.degree,self.degree])
507+
self.e_lambda_mats = np.empty([self.num_classes,self.degree,self.degree])
508+
self.e_ln_lambda_dets = np.empty(self.num_classes)
509+
self.e_ln_pi_vec = np.empty(self.num_classes)
510+
497511
# p_params
498512
self.p_pi_vec = np.empty([self.num_classes])
499513
self.p_mu_vecs = np.empty([self.num_classes,self.degree])
@@ -564,6 +578,15 @@ def set_h0_params(
564578
self.h0_w_mats[:] = h0_w_mats
565579
self.h0_w_mats_inv[:] = np.linalg.inv(self.h0_w_mats)
566580

581+
self.LN_C_H0_ALPHA = gammaln(self.h0_alpha_vec.sum()) - gammaln(self.h0_alpha_vec).sum()
582+
self.LN_B_H0_W_NUS = (
583+
- self.h0_nus*np.linalg.slogdet(self.h0_w_mats)[1]
584+
- self.h0_nus*self.degree*np.log(2.0)
585+
- self.degree*(self.degree-1)/2.0*np.log(np.pi)
586+
- np.sum(gammaln((self.h0_nus[:,np.newaxis]-np.arange(self.degree)) / 2.0),
587+
axis=1) * 2.0
588+
) / 2.0
589+
567590
self.reset_hn_params()
568591

569592
def get_h0_params(self):
@@ -689,6 +712,67 @@ def overwrite_h0_params(self):
689712

690713
self.calc_pred_dist()
691714

715+
def calc_vl(self):
716+
self.e_lambda_mats = self.hn_nus[:,np.newaxis,np.newaxis] * self.hn_w_mats
717+
self.e_ln_lambda_dets = (np.sum(digamma((self.hn_nus[:,np.newaxis]-np.arange(self.degree)) / 2.0),axis=1)
718+
+ self.degree*np.log(2.0)
719+
- np.linalg.slogdet(self.hn_w_mats_inv)[1])
720+
self.e_ln_pi_vec = digamma(self.hn_alpha_vec) - digamma(self.hn_alpha_vec.sum())
721+
722+
# tentative
723+
self.ns = np.ones(self.num_classes) * 10
724+
self.s_mats = np.tile(np.identity(self.degree),[self.num_classes,1,1]) * 5
725+
self.r_vecs = np.ones([20,self.degree])/self.degree
726+
self.x_bar_vecs = np.ones([self.num_classes,self.degree])
727+
728+
vl = 0.0
729+
730+
# E[ln p(X|Z,mu,Lambda)]
731+
vl += np.sum(
732+
self.ns
733+
* (self.e_ln_lambda_dets - self.degree / self.hn_kappas
734+
- (self.s_mats * self.e_lambda_mats).sum(axis=(1,2))
735+
- ((self.x_bar_vecs - self.hn_m_vecs)[:,np.newaxis,:]
736+
@ self.e_lambda_mats
737+
@ (self.x_bar_vecs - self.hn_m_vecs)[:,:,np.newaxis]
738+
)[:,0,0]
739+
- self.degree * np.log(2*np.pi)
740+
)
741+
) / 2.0
742+
743+
# E[ln p(Z|pi)]
744+
vl += (self.ns * self.e_ln_pi_vec).sum()
745+
746+
# E[ln p(pi)]
747+
vl += self.LN_C_H0_ALPHA + ((self.h0_alpha_vec - 1) * self.e_ln_pi_vec).sum()
748+
749+
# E[ln p(mu,Lambda)]
750+
vl += np.sum(
751+
self.degree * (np.log(self.h0_kappas) - np.log(2*np.pi) - self.h0_kappas/self.hn_kappas)
752+
- ((self.hn_m_vecs - self.h0_m_vecs)[:,np.newaxis,:]
753+
@ self.e_lambda_mats
754+
@ (self.hn_m_vecs - self.h0_m_vecs)[:,:,np.newaxis])[:,0,0]
755+
+ 2.0 * self.LN_B_H0_W_NUS
756+
+ (self.h0_nus - self.degree) / 2.0 * self.e_ln_lambda_dets
757+
- np.sum(self.h0_w_mats_inv * self.hn_w_mats,axis=(1,2))
758+
) / 2.0
759+
760+
# E[ln q(Z|pi)]
761+
vl -= np.sum(xlogy(self.r_vecs,self.r_vecs))
762+
763+
# E[ln q(pi)]
764+
vl += ss_dirichlet.entropy(self.hn_alpha_vec)
765+
766+
# E[ln q(mu,Lambda)]
767+
vl += np.sum(
768+
+ self.degree * (1.0 + np.log(2.0*np.pi) - np.log(self.hn_kappas))
769+
- self.LN_B_H0_W_NUS * 2.0
770+
- (self.hn_nus-self.degree)*self.e_ln_lambda_dets
771+
+ self.hn_nus * self.degree
772+
) / 2.0
773+
774+
return vl
775+
692776
def update_posterior(self,x):
693777
pass
694778
# """Update the hyperparameters of the posterior distribution using traning data.

bayesml/gaussianmixture/test.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
from bayesml import gaussianmixture
22
import numpy as np
33

4-
model = gaussianmixture.LearnModel(num_classes=2, degree=2, h0_w_mats=np.identity(2)*2)
5-
6-
print(model.get_hn_params())
7-
model.set_hn_params(hn_m_vecs=np.ones(2))
8-
print(model.get_hn_params())
4+
model = gaussianmixture.LearnModel(num_classes=3, degree=2, h0_w_mats=np.identity(2)*2)
5+
print(model.calc_vl())

0 commit comments

Comments
 (0)