Skip to content

Commit 4e1bccb

Browse files
committed
Add estimate_latent_vars
1 parent 757ac61 commit 4e1bccb

File tree

2 files changed

+125
-18
lines changed

2 files changed

+125
-18
lines changed

bayesml/hiddenmarkovnormal/_hiddenmarkovnormal.py

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,10 @@ def __init__(
579579
self.p_lambda_mats = np.empty([self.c_num_classes,self.c_degree,self.c_degree])
580580
self.p_lambda_mats_inv = np.empty([self.c_num_classes,self.c_degree,self.c_degree])
581581

582+
# for Viterbi
583+
self.omega_vecs = None
584+
self.phi_vecs = None
585+
582586
self.set_h0_params(
583587
h0_eta_vec,
584588
h0_zeta_vecs,
@@ -1012,6 +1016,7 @@ def update_posterior(
10121016
Parameters
10131017
----------
10141018
x : numpy.ndarray
1019+
(sample_length,c_degree)-dimensional ndarray.
10151020
All the elements must be real number.
10161021
max_itr : int, optional
10171022
maximum number of iterations, by default 100
@@ -1395,38 +1400,138 @@ def pred_and_update(
13951400
)
13961401
return prediction
13971402

1398-
def estimate_latent_vars(self,x,loss=''):
1403+
def estimate_latent_vars(self,x,loss='0-1',viterbi=True):
13991404
"""Estimate latent variables under the given criterion.
14001405
1401-
Note that the criterion is independently applied to each data point.
1406+
If the ``viterbi`` option is ``True``, this function estimates
1407+
the latent variables maximizing the joint distribution.
1408+
If ``False``, this function independently estimates the latent
1409+
variables at each time point.
14021410
14031411
Parameters
14041412
----------
1413+
x : numpy.ndarray
1414+
(sample_length,c_degree)-dimensional ndarray.
1415+
All the elements must be real number.
14051416
loss : str, optional
1406-
Loss function underlying the Bayes risk function, by default \"xxx\".
1407-
This function supports \"xxx\", \"xxx\", and \"xxx\".
1417+
Loss function underlying the Bayes risk function, by default \"0-1\".
1418+
If the ``viterbi`` option is ``True``, this function supports only \"0-1\".
1419+
Otherwise, \"0-1\", \"squared\", and \"KL\" are supported.
1420+
viterbi : bool, optional
1421+
If ``True``, this function estimates the latent variables as a sequence.
14081422
14091423
Returns
14101424
-------
14111425
estimates : numpy.ndarray
14121426
The estimated values under the given loss function.
1413-
If the loss function is \"xxx\", the posterior distribution will be returned
1414-
as a numpy.ndarray whose elements consist of occurence probabilities.
1427+
If the ``viterbi`` option is ``False`` and loss function is \"KL\",
1428+
a marginalized posterior distribution will be returned as
1429+
a numpy.ndarray whose elements consist of occurence
1430+
probabilities for each latent variabl.
14151431
"""
1416-
pass
1432+
_check.float_vecs(x,'x',DataFormatError)
1433+
if x.shape[-1] != self.c_degree:
1434+
raise(DataFormatError(
1435+
"x.shape[-1] must be self.c_degree: "
1436+
+ f"x.shape[-1]={x.shape[-1]}, self.c_degree={self.c_degree}"))
1437+
x = x.reshape(-1,self.c_degree)
1438+
self._length = x.shape[0]
1439+
z_hat = np.zeros([self._length,self.c_num_classes],dtype=int)
1440+
self._ln_rho = np.zeros([self._length,self.c_num_classes])
1441+
self._rho = np.ones([self._length,self.c_num_classes])
14171442

1418-
def estimate_latent_vars_and_update(self):
1443+
if viterbi:
1444+
if loss == '0-1':
1445+
self.omega_vecs = np.zeros([self._length,self.c_num_classes])
1446+
self.phi_vecs = np.zeros([self._length,self.c_num_classes],dtype=int)
1447+
self._calc_rho(x)
1448+
1449+
self.omega_vecs[0] = self._ln_rho[0] + self._ln_pi_tilde_vec
1450+
for i in range(1,self._length):
1451+
self.omega_vecs[i] = self._ln_rho[i] + np.max(self._ln_a_tilde_mat + self.omega_vecs[i-1,:,np.newaxis],axis=0)
1452+
self.phi_vecs[i] = np.argmax(self._ln_a_tilde_mat + self.omega_vecs[i-1,:,np.newaxis],axis=0)
1453+
1454+
tmp_k = np.argmax(self.omega_vecs[-1])
1455+
z_hat[-1,tmp_k] = 1
1456+
for i in range(self._length-2,-1,-1):
1457+
tmp_k = self.phi_vecs[i+1,tmp_k]
1458+
z_hat[i,tmp_k] = 1
1459+
return z_hat
1460+
else:
1461+
raise(CriteriaError(f"loss=\"{loss}\" is unsupported. "
1462+
+"When viterbi == True, this function supports only \"0-1\"."))
1463+
1464+
else:
1465+
self.alpha_vecs = np.ones([self._length,self.c_num_classes])/self.c_num_classes
1466+
self.beta_vecs = np.ones([self._length,self.c_num_classes])
1467+
self.gamma_vecs = np.ones([self._length,self.c_num_classes])/self.c_num_classes
1468+
self.xi_mats = np.zeros([self._length,self.c_num_classes,self.c_num_classes])/(self.c_num_classes**2)
1469+
self._cs = np.ones([self._length])
1470+
self._update_q_z(x)
1471+
if loss == "squared" or loss == "KL":
1472+
return self.gamma_vecs
1473+
elif loss == "0-1":
1474+
return np.identity(self.c_num_classes,dtype=int)[np.argmax(self.gamma_vecs,axis=1)]
1475+
else:
1476+
raise(CriteriaError(f"loss=\"{loss}\" is unsupported. "
1477+
+"When viterbi == False, This function supports \"squared\", \"0-1\", and \"KL\"."))
1478+
1479+
def estimate_latent_vars_and_update(
1480+
self,
1481+
x,
1482+
loss="0-1",
1483+
viterbi=True,
1484+
max_itr=100,
1485+
num_init=10,
1486+
tolerance=1.0E-8,
1487+
init_type='subsampling'
1488+
):
14191489
"""Estimate latent variables and update the posterior sequentially.
14201490
14211491
h0_params will be overwritten by current hn_params
14221492
before updating hn_params by x
14231493
14241494
Parameters
14251495
----------
1496+
x : numpy.ndarray
1497+
It must be a `c_degree`-dimensional vector
1498+
loss : str, optional
1499+
Loss function underlying the Bayes risk function, by default \"0-1\".
1500+
If the ``viterbi`` option is ``True``, this function supports only \"0-1\".
1501+
Otherwise, \"0-1\", \"squared\", and \"KL\" are supported.
1502+
viterbi : bool, optional
1503+
If ``True``, this function estimates the latent variables as a sequence.
1504+
max_itr : int, optional
1505+
maximum number of iterations, by default 100
1506+
num_init : int, optional
1507+
number of initializations, by default 10
1508+
tolerance : float, optional
1509+
convergence croterion of variational lower bound, by default 1.0E-8
1510+
init_type : str, optional
1511+
type of initialization, by default 'random_responsibility'
1512+
* 'random_responsibility': randomly assign responsibility to r_vecs
1513+
* 'subsampling': for each latent class, extract a subsample whose size is int(np.sqrt(x.shape[0])).
1514+
and use its mean and covariance matrix as an initial values of hn_m_vecs and hn_lambda_mats.
14261515
14271516
Returns
14281517
-------
1429-
predicted_value : numpy.ndarray
1518+
estimates : numpy.ndarray
14301519
The estimated values under the given loss function.
1520+
If the ``viterbi`` option is ``False`` and loss function is \"KL\",
1521+
a marginalized posterior distribution will be returned as
1522+
a numpy.ndarray whose elements consist of occurence
1523+
probabilities for each latent variabl.
14311524
"""
1432-
pass
1525+
_check.float_vec(x,'x',DataFormatError)
1526+
if x.shape != (self.c_degree,):
1527+
raise(DataFormatError(f"x must be a 1-dimensional float array whose size is c_degree: {self.c_degree}."))
1528+
z_hat = self.estimate_latent_vars(x,loss=loss,viterbi=viterbi)
1529+
self.overwrite_h0_params()
1530+
self.update_posterior(
1531+
x,
1532+
max_itr=max_itr,
1533+
num_init=num_init,
1534+
tolerance=tolerance,
1535+
init_type=init_type
1536+
)
1537+
return z_hat

bayesml/hiddenmarkovnormal/_hiddenmarkovnormal_test.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,25 @@
44

55
gen_model = hiddenmarkovnormal.GenModel(
66
c_num_classes=2,
7-
c_degree=2,
8-
mu_vecs=np.array([[5,5],[-5,-5]]),
7+
c_degree=1,
8+
mu_vecs=np.array([[5],[-5]]),
99
a_mat=np.array([[0.95,0.05],[0.1,0.9]])
1010
)
1111
# model.visualize_model()
1212
x,z = gen_model.gen_sample(sample_length=200)
13+
# plt.plot(np.arange(100),x)
14+
# plt.show()
1315

1416
learn_model = hiddenmarkovnormal.LearnModel(
15-
c_num_classes=3,
16-
c_degree=2,
17+
c_num_classes=2,
18+
c_degree=1,
1719
)
1820
learn_model.update_posterior(x[:150])#,init_type='random_responsibility')
1921

20-
pred_values = np.empty([50,2])
22+
z_hat = np.empty([50,2])
2123
for i in range(50):
22-
pred_values[i] = learn_model.pred_and_update(x[150+i],loss="0-1")
24+
z_hat[i] = learn_model.estimate_latent_vars_and_update(x[150+i],loss="0-1")
2325

24-
plt.plot(np.arange(50),pred_values[:,0])
25-
plt.plot(np.arange(50),x[150:,0])
26+
plt.plot(np.arange(50),z_hat[:,0])
27+
plt.plot(np.arange(50),z[150:,0])
2628
plt.show()

0 commit comments

Comments
 (0)