Skip to content

Commit d7ee395

Browse files
committed
estimate_latent_vars
1 parent a6e6e0d commit d7ee395

File tree

2 files changed

+162
-36
lines changed

2 files changed

+162
-36
lines changed

bayesml/gaussianmixture/_gaussianmixture.py

Lines changed: 149 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ def __init__(
482482
self.h0_w_mats = np.tile(np.identity(self.degree),[self.num_classes,1,1])
483483
self.h0_w_mats_inv = np.linalg.inv(self.h0_w_mats)
484484

485-
self._LN_C_H0_ALPHA = 0.0
486-
self._LN_B_H0_W_NUS = np.empty(self.num_classes)
485+
self._ln_c_h0_alpha = 0.0
486+
self._ln_b_h0_w_nus = np.empty(self.num_classes)
487487

488488
# hn_params
489489
self.hn_alpha_vec = np.empty([self.num_classes])
@@ -585,15 +585,7 @@ def set_h0_params(
585585
self.h0_w_mats[:] = h0_w_mats
586586
self.h0_w_mats_inv[:] = np.linalg.inv(self.h0_w_mats)
587587

588-
self._LN_C_H0_ALPHA = gammaln(self.h0_alpha_vec.sum()) - gammaln(self.h0_alpha_vec).sum()
589-
self._LN_B_H0_W_NUS = (
590-
- self.h0_nus*np.linalg.slogdet(self.h0_w_mats)[1]
591-
- self.h0_nus*self.degree*np.log(2.0)
592-
- self.degree*(self.degree-1)/2.0*np.log(np.pi)
593-
- np.sum(gammaln((self.h0_nus[:,np.newaxis]-np.arange(self.degree)) / 2.0),
594-
axis=1) * 2.0
595-
) / 2.0
596-
588+
self.calc_prior_char()
597589
self.reset_hn_params()
598590

599591
def get_h0_params(self):
@@ -669,6 +661,9 @@ def set_hn_params(
669661
self.hn_w_mats[:] = hn_w_mats
670662
self.hn_w_mats_inv[:] = np.linalg.inv(self.hn_w_mats)
671663

664+
self._calc_q_pi_char()
665+
self._calc_q_lambda_char()
666+
672667
self.calc_pred_dist()
673668

674669
def get_hn_params(self):
@@ -707,6 +702,16 @@ def reset_hn_params(self):
707702

708703
self.calc_pred_dist()
709704

705+
def calc_prior_char(self):
706+
self._ln_c_h0_alpha = gammaln(self.h0_alpha_vec.sum()) - gammaln(self.h0_alpha_vec).sum()
707+
self._ln_b_h0_w_nus = (
708+
- self.h0_nus*np.linalg.slogdet(self.h0_w_mats)[1]
709+
- self.h0_nus*self.degree*np.log(2.0)
710+
- self.degree*(self.degree-1)/2.0*np.log(np.pi)
711+
- np.sum(gammaln((self.h0_nus[:,np.newaxis]-np.arange(self.degree)) / 2.0),
712+
axis=1) * 2.0
713+
) / 2.0
714+
710715
def overwrite_h0_params(self):
711716
"""Overwrite the initial values of the hyperparameters of the posterior distribution by the learned values.
712717
@@ -720,7 +725,8 @@ def overwrite_h0_params(self):
720725
self.h0_w_mats[:] = self.hn_w_mats
721726
self.h0_w_mats_inv = np.linalg.inv(self.h0_w_mats)
722727

723-
self.calc_pred_dist()
728+
self.calc_prior_char()
729+
self.reset_hn_params()
724730

725731
def calc_vl(self):
726732
# E[ln p(X|Z,mu,Lambda)]
@@ -740,7 +746,7 @@ def calc_vl(self):
740746
self._vl_p_z = (self.ns * self._e_ln_pi_vec).sum()
741747

742748
# E[ln p(pi)]
743-
self._vl_p_pi = self._LN_C_H0_ALPHA + ((self.h0_alpha_vec - 1) * self._e_ln_pi_vec).sum()
749+
self._vl_p_pi = self._ln_c_h0_alpha + ((self.h0_alpha_vec - 1) * self._e_ln_pi_vec).sum()
744750

745751
# E[ln p(mu,Lambda)]
746752
self._vl_p_mu_lambda = np.sum(
@@ -749,7 +755,7 @@ def calc_vl(self):
749755
- self.h0_kappas * ((self.hn_m_vecs - self.h0_m_vecs)[:,np.newaxis,:]
750756
@ self._e_lambda_mats
751757
@ (self.hn_m_vecs - self.h0_m_vecs)[:,:,np.newaxis])[:,0,0]
752-
+ 2.0 * self._LN_B_H0_W_NUS
758+
+ 2.0 * self._ln_b_h0_w_nus
753759
+ (self.h0_nus - self.degree) * self._e_ln_lambda_dets
754760
- np.sum(self.h0_w_mats_inv * self._e_lambda_mats,axis=(1,2))
755761
) / 2.0
@@ -838,7 +844,7 @@ def _update_q_z(self,x):
838844
self._calc_n_x_bar_s(x)
839845

840846
def _init_subsampling(self,x):
841-
_size = int(np.sqrt(x.shape[0])) + 1
847+
_size = int(np.sqrt(x.shape[0]))
842848
for k in range(self.num_classes):
843849
_subsample = self.rng.choice(x,size=_size,replace=False,axis=0,shuffle=False)
844850
self.hn_m_vecs[k] = _subsample.sum(axis=0) / _size
@@ -850,7 +856,14 @@ def _init_subsampling(self,x):
850856
self._calc_q_pi_char()
851857
self._calc_q_lambda_char()
852858

853-
def update_posterior(self,x,max_itr=100,num_init=10,tolerance=1.0E-8,init_type='subsampling'):
859+
def update_posterior(
860+
self,
861+
x,
862+
max_itr=100,
863+
num_init=10,
864+
tolerance=1.0E-8,
865+
init_type='subsampling'
866+
):
854867
"""Update the hyperparameters of the posterior distribution using traning data.
855868
856869
Parameters
@@ -863,9 +876,14 @@ def update_posterior(self,x,max_itr=100,num_init=10,tolerance=1.0E-8,init_type='
863876
number of initializations, by default 10
864877
tolerance : float, optional
865878
convergence croterion of variational lower bound, by default 1.0E-8
879+
init_type : str, optional
880+
type of initialization, by default 'subsampling'
881+
* 'subsampling': for each latent class, extract a subsample whose size is int(np.sqrt(x.shape[0])).
882+
and use its mean and covariance matrix as an initial values of hn_m_vecs and hn_lambda_mats.
883+
* 'random_responsibility': randomly assign responsibility to r_vecs
866884
"""
867885
_check.float_vecs(x,'x',DataFormatError)
868-
if self.degree > 1 and x.shape[-1] != self.degree:
886+
if x.shape[-1] != self.degree:
869887
raise(DataFormatError(
870888
"x.shape[-1] must be self.degree: "
871889
+ f"x.shape[-1]={x.shape[-1]}, self.degree={self.degree}"))
@@ -930,7 +948,6 @@ def update_posterior(self,x,max_itr=100,num_init=10,tolerance=1.0E-8,init_type='
930948
self._calc_q_lambda_char()
931949
self._update_q_z(x)
932950

933-
934951
def estimate_params(self,loss="squared"):
935952
"""Estimate the parameter of the stochastic data generative model under the given criterion.
936953
@@ -1135,7 +1152,15 @@ def make_prediction(self,loss="squared"):
11351152
raise(CriteriaError(f"loss={loss} is unsupported. "
11361153
+"This function supports \"squared\" and \"0-1\"."))
11371154

1138-
def pred_and_update(self,x,loss="squared"):
1155+
def pred_and_update(
1156+
self,
1157+
x,
1158+
loss="squared",
1159+
max_itr=100,
1160+
num_init=10,
1161+
tolerance=1.0E-8,
1162+
init_type='random_responsibility'
1163+
):
11391164
"""Predict a new data point and update the posterior sequentially.
11401165
11411166
h0_params will be overwritten by current hn_params
@@ -1148,6 +1173,17 @@ def pred_and_update(self,x,loss="squared"):
11481173
loss : str, optional
11491174
Loss function underlying the Bayes risk function, by default \"squared\".
11501175
This function supports \"squared\" and \"0-1\".
1176+
max_itr : int, optional
1177+
maximum number of iterations, by default 100
1178+
num_init : int, optional
1179+
number of initializations, by default 10
1180+
tolerance : float, optional
1181+
convergence croterion of variational lower bound, by default 1.0E-8
1182+
init_type : str, optional
1183+
type of initialization, by default 'random_responsibility'
1184+
* 'random_responsibility': randomly assign responsibility to r_vecs
1185+
* 'subsampling': for each latent class, extract a subsample whose size is int(np.sqrt(x.shape[0])).
1186+
and use its mean and covariance matrix as an initial values of hn_m_vecs and hn_lambda_mats.
11511187
11521188
Returns
11531189
-------
@@ -1160,5 +1196,98 @@ def pred_and_update(self,x,loss="squared"):
11601196
self.calc_pred_dist()
11611197
prediction = self.make_prediction(loss=loss)
11621198
self.overwrite_h0_params()
1163-
self.update_posterior(x[np.newaxis,:])
1199+
self.update_posterior(
1200+
x[np.newaxis,:],
1201+
max_itr=max_itr,
1202+
num_init=num_init,
1203+
tolerance=tolerance,
1204+
init_type=init_type
1205+
)
11641206
return prediction
1207+
1208+
def estimate_latent_vars(self,x,loss="0-1"):
1209+
"""Estimate latent variables corresponding to `x` under the given criterion.
1210+
1211+
Note that the criterion is independently applied to each data point.
1212+
1213+
Parameters
1214+
----------
1215+
loss : str, optional
1216+
Loss function underlying the Bayes risk function, by default \"0-1\".
1217+
This function supports \"squared\", \"0-1\", and \"KL\".
1218+
1219+
Returns
1220+
-------
1221+
Estimates : numpy.ndarray
1222+
The estimated values under the given loss function.
1223+
If the loss function is \"KL\", the posterior distribution will be returned
1224+
as a numpy.ndarray whose elements consist of occurence probabilities.
1225+
"""
1226+
_check.float_vecs(x,'x',DataFormatError)
1227+
if x.shape[-1] != self.degree:
1228+
raise(DataFormatError(
1229+
"x.shape[-1] must be self.degree: "
1230+
+ f"x.shape[-1]={x.shape[-1]}, self.degree={self.degree}"))
1231+
x = x.reshape(-1,self.degree)
1232+
self._ln_rho = np.empty([x.shape[0],self.num_classes])
1233+
self.r_vecs = np.empty([x.shape[0],self.num_classes])
1234+
self._update_q_z(x)
1235+
1236+
if loss == "squared":
1237+
return self.r_vecs
1238+
elif loss == "0-1":
1239+
return np.identity(self.num_classes,dtype=int)[np.argmax(self.r_vecs,axis=1)]
1240+
elif loss == "KL":
1241+
return self.r_vecs
1242+
else:
1243+
raise(CriteriaError(f"loss={loss} is unsupported. "
1244+
+"This function supports \"squared\", \"0-1\", and \"KL\"."))
1245+
1246+
def estimate_latent_vars_and_update(
1247+
self,
1248+
x,
1249+
loss="0-1",
1250+
max_itr=100,
1251+
num_init=10,
1252+
tolerance=1.0E-8,
1253+
init_type='subsampling'
1254+
):
1255+
"""Estimate latent variables and update the posterior sequentially.
1256+
1257+
h0_params will be overwritten by current hn_params
1258+
before updating hn_params by x
1259+
1260+
Parameters
1261+
----------
1262+
x : numpy.ndarray
1263+
It must be a `degree`-dimensional vector
1264+
loss : str, optional
1265+
Loss function underlying the Bayes risk function, by default \"0-1\".
1266+
This function supports \"squared\" and \"0-1\".
1267+
max_itr : int, optional
1268+
maximum number of iterations, by default 100
1269+
num_init : int, optional
1270+
number of initializations, by default 10
1271+
tolerance : float, optional
1272+
convergence croterion of variational lower bound, by default 1.0E-8
1273+
init_type : str, optional
1274+
type of initialization, by default 'subsampling'
1275+
* 'subsampling': for each latent class, extract a subsample whose size is int(np.sqrt(x.shape[0])).
1276+
and use its mean and covariance matrix as an initial values of hn_m_vecs and hn_lambda_mats.
1277+
* 'random_responsibility': randomly assign responsibility to r_vecs
1278+
1279+
Returns
1280+
-------
1281+
Predicted_value : numpy.ndarray
1282+
The estimated values under the given loss function.
1283+
"""
1284+
z_hat = self.estimate_latent_vars(x,loss=loss)
1285+
self.overwrite_h0_params()
1286+
self.update_posterior(
1287+
x,
1288+
max_itr=max_itr,
1289+
num_init=num_init,
1290+
tolerance=tolerance,
1291+
init_type=init_type
1292+
)
1293+
return z_hat

bayesml/gaussianmixture/test.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@
33
from matplotlib import pyplot as plt
44
from time import time
55

6-
# gen_model = gaussianmixture.GenModel(
7-
# num_classes=2,
8-
# degree=1,
9-
# mu_vecs=np.array([[-2],[2]]),
10-
# )
11-
# gen_model.save_sample('GMM_sample',sample_size=1000)
6+
gen_model = gaussianmixture.GenModel(
7+
num_classes=2,
8+
degree=2,
9+
mu_vecs=np.array([[-2,-2],[2,2]]),
10+
)
11+
x,z = gen_model.gen_sample(sample_size=100)
12+
print(x.shape)
1213

13-
x = np.load('GMM_sample.npz')['x']
14-
learn_model = gaussianmixture.LearnModel(num_classes=4, degree=1,seed=123)
15-
16-
start = time()
17-
learn_model.update_posterior(x)
18-
end = time()
19-
print(end-start)
20-
print(learn_model.hn_m_vecs)
21-
22-
# learn_model.visualize_posterior()
14+
learn_model = gaussianmixture.LearnModel(num_classes=10, degree=2, h0_alpha_vec=10)
15+
for i in range(100):
16+
learn_model.pred_and_update(x[i])
17+
plt.scatter(x[:i+1,0],x[:i+1,1])
18+
plt.show()
19+
learn_model.visualize_posterior()

0 commit comments

Comments
 (0)