Skip to content

Commit 6d2d639

Browse files
Merge pull request #38 from yuta-nakahara/develop-hiddenmarkovnormal-GenModel
Develop hiddenmarkovnormal gen model
2 parents 13cb44b + 9b7dc1d commit 6d2d639

File tree

9 files changed

+2006
-49
lines changed

9 files changed

+2006
-49
lines changed

bayesml/hiddenmarkovnormal/_gaussianmixture_for_ref.py

Lines changed: 1211 additions & 0 deletions
Large diffs are not rendered by default.

bayesml/hiddenmarkovnormal/_hiddenmarkovnormal.py

Lines changed: 248 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,59 @@
1616
from .. import _check
1717

1818
class GenModel(base.Generative):
19+
"""The stochastic data generative model and the prior distribution.
20+
21+
Parameters
22+
----------
23+
c_num_classes : int
24+
a positive integer
25+
c_degree : int
26+
a positive integer
27+
pi_vec : numpy.ndarray, optional
28+
A vector of real numbers in :math:`[0, 1]`,
29+
by default [1/c_num_classes, 1/c_num_classes, ... , 1/c_num_classes].
30+
Sum of its elements must be 1.0.
31+
a_mat : numpy.ndarray, optional
32+
A matrix of real numbers in :math:`[0, 1]`,
33+
by default a matrix obtained by stacking
34+
[1/c_num_classes, 1/c_num_classes, ... , 1/c_num_classes].
35+
Sum of the elements of each row vector must be 1.0.
36+
If a single vector is input, will be broadcasted.
37+
mu_vecs : numpy.ndarray, optional
38+
Vectors of real numbers,
39+
by default zero vectors.
40+
If a single vector is input, will be broadcasted.
41+
lambda_mats : numpy.ndarray, optional
42+
Positive definite symetric matrices,
43+
by default the identity matrices.
44+
If a single matrix is input, it will be broadcasted.
45+
h_eta_vec : numpy.ndarray, optional
46+
A vector of positive real numbers,
47+
by default [1/2, 1/2, ... , 1/2]
48+
h_zeta_vecs : numpy.ndarray, optional
49+
Vectors of positive numbers,
50+
by default vectors whose elements are all 1/2
51+
If a single vector is input, will be broadcasted.
52+
h_m_vecs : numpy.ndarray, optional
53+
Vectors of real numbers,
54+
by default zero vectors
55+
If a single vector is input, will be broadcasted.
56+
h_kappas : float or numpy.ndarray, optional
57+
Positive real numbers,
58+
by default [1.0, 1.0, ... , 1.0].
59+
If a single real number is input, it will be broadcasted.
60+
h_nus : float or numpy.ndarray, optional
61+
Real numbers greater than ``c_degree-1``,
62+
by default [c_degree, c_degree, ... , c_degree]
63+
If a single real number is input, it will be broadcasted.
64+
h_w_mats : numpy.ndarray, optional
65+
Positive definite symetric matrices,
66+
by default the identity matrices.
67+
If a single matrix is input, it will be broadcasted.
68+
seed : {None, int}, optional
69+
A seed to initialize numpy.random.default_rng(),
70+
by default None
71+
"""
1972
def __init__(
2073
self,
2174
c_num_classes,
@@ -77,14 +130,24 @@ def set_params(
77130
78131
Parameters
79132
----------
80-
pi_vec : numpy.ndarray
81-
a real vector in :math:`[0, 1]^K`. The sum of its elements must be 1.
82-
a_mat : numpy.ndarray
83-
a real matrix in :math:`[0, 1]^{KxK}`. The sum of each row elements must be 1.
84-
mu_vecs : numpy.ndarray
85-
vectors of real numbers
86-
lambda_mats : numpy.ndarray
87-
positive definite symetric matrices
133+
pi_vec : numpy.ndarray, optional
134+
A vector of real numbers in :math:`[0, 1]`,
135+
by default [1/c_num_classes, 1/c_num_classes, ... , 1/c_num_classes].
136+
Sum of its elements must be 1.0.
137+
a_mat : numpy.ndarray, optional
138+
A matrix of real numbers in :math:`[0, 1]`,
139+
by default a matrix obtained by stacking
140+
[1/c_num_classes, 1/c_num_classes, ... , 1/c_num_classes].
141+
Sum of the elements of each row vector must be 1.0.
142+
If a single vector is input, will be broadcasted.
143+
mu_vecs : numpy.ndarray, optional
144+
Vectors of real numbers,
145+
by default zero vectors.
146+
If a single vector is input, will be broadcasted.
147+
lambda_mats : numpy.ndarray, optional
148+
Positive definite symetric matrices,
149+
by default the identity matrices.
150+
If a single matrix is input, it will be broadcasted.
88151
"""
89152
if pi_vec is not None:
90153
_check.float_vec_sum_1(pi_vec, "pi_vec", ParameterFormatError)
@@ -131,7 +194,34 @@ def set_h_params(
131194
h_nus=None,
132195
h_w_mats=None,
133196
):
197+
"""Set the hyperparameters of the prior distribution.
134198
199+
Parameters
200+
----------
201+
h_eta_vec : numpy.ndarray, optional
202+
A vector of positive real numbers,
203+
by default [1/2, 1/2, ... , 1/2]
204+
h_zeta_vecs : numpy.ndarray, optional
205+
Vectors of positive numbers,
206+
by default vectors whose elements are all 1/2
207+
If a single vector is input, will be broadcasted.
208+
h_m_vecs : numpy.ndarray, optional
209+
Vectors of real numbers,
210+
by default zero vectors
211+
If a single vector is input, will be broadcasted.
212+
h_kappas : float or numpy.ndarray, optional
213+
Positive real numbers,
214+
by default [1.0, 1.0, ... , 1.0].
215+
If a single real number is input, it will be broadcasted.
216+
h_nus : float or numpy.ndarray, optional
217+
Real numbers greater than ``c_degree-1``,
218+
by default [c_degree, c_degree, ... , c_degree]
219+
If a single real number is input, it will be broadcasted.
220+
h_w_mats : numpy.ndarray, optional
221+
Positive definite symetric matrices,
222+
by default the identity matrices.
223+
If a single matrix is input, it will be broadcasted.
224+
"""
135225
if h_eta_vec is not None:
136226
_check.pos_floats(h_eta_vec,'h_eta_vec',ParameterFormatError)
137227
self.h_eta_vec[:] = h_eta_vec
@@ -171,12 +261,34 @@ def set_h_params(
171261
self.h_w_mats[:] = h_w_mats
172262

173263
def get_params(self):
264+
"""Get the parameter of the sthocastic data generative model.
265+
266+
Returns
267+
-------
268+
params : {str:float, numpy.ndarray}
269+
* ``"pi_vec"`` : The value of ``self.pi_vec``
270+
* ``"a_mat"`` : The value of ``self.a_mat``
271+
* ``"mu_vecs"`` : The value of ``self.mu_vecs``
272+
* ``"lambda_mats"`` : The value of ``self.lambda_mats``
273+
"""
174274
return {'pi_vec':self.pi_vec,
175275
'a_mat':self.a_mat,
176276
'mu_vecs':self.mu_vecs,
177277
'lambda_mats': self.lambda_mats}
178278

179279
def get_h_params(self):
280+
"""Get the hyperparameters of the prior distribution.
281+
282+
Returns
283+
-------
284+
h_params : {str:float, np.ndarray}
285+
* ``"h_eta_vec"`` : The value of ``self.h_eta_vec``
286+
* ``"h_zeta_vecs"`` : The value of ``self.h_zeta_vecs``
287+
* ``"h_m_vecs"`` : The value of ``self.h_m_vecs``
288+
* ``"h_kappas"`` : The value of ``self.h_kappas``
289+
* ``"h_nus"`` : The value of ``self.h_nus``
290+
* ``"h_w_mats"`` : The value of ``self.h_w_mats``
291+
"""
180292
return {'h_eta_vec':self.h_eta_vec,
181293
'h_zeta_vecs':self.h_zeta_vecs,
182294
'h_m_vecs':self.h_m_vecs,
@@ -185,17 +297,138 @@ def get_h_params(self):
185297
'h_w_mats':self.h_w_mats}
186298

187299
def gen_params(self):
188-
pass
300+
"""Generate the parameter from the prior distribution.
301+
302+
To confirm the generated vaules, use `self.get_params()`.
303+
"""
304+
self.pi_vec[:] = self.rng.dirichlet(self.h_eta_vec)
305+
for k in range(self.c_num_classes):
306+
self.a_mat[k] = self.rng.dirichlet(self.h_zeta_vecs[k])
307+
for k in range(self.c_num_classes):
308+
self.lambda_mats[k] = ss_wishart.rvs(df=self.h_nus[k],scale=self.h_w_mats[k],random_state=self.rng)
309+
self.mu_vecs[k] = self.rng.multivariate_normal(mean=self.h_m_vecs[k],cov=np.linalg.inv(self.h_kappas[k]*self.lambda_mats[k]))
189310

190-
def gen_sample(self):
191-
pass
311+
def gen_sample(self,sample_length):
312+
"""Generate a sample from the stochastic data generative model.
313+
314+
Parameters
315+
----------
316+
sample_length : int
317+
A positive integer
318+
319+
Returns
320+
-------
321+
x : numpy ndarray
322+
2-dimensional array whose shape is
323+
``(sample_length,c_degree)`` .
324+
Its elements are real numbers.
325+
z : numpy ndarray
326+
2-dimensional array whose shape is
327+
``(sample_length,c_num_classes)``
328+
whose rows are one-hot vectors.
329+
"""
330+
_check.pos_int(sample_length,'sample_length',DataFormatError)
331+
z = np.zeros([sample_length,self.c_num_classes],dtype=int)
332+
x = np.empty([sample_length,self.c_degree])
333+
_lambda_mats_inv = np.linalg.inv(self.lambda_mats)
334+
335+
# i=0
336+
k = self.rng.choice(self.c_num_classes,p=self.pi_vec)
337+
z[0,k] = 1
338+
x[0] = self.rng.multivariate_normal(mean=self.mu_vecs[k],cov=_lambda_mats_inv[k])
339+
# i>0
340+
for i in range(1,sample_length):
341+
k = self.rng.choice(self.c_num_classes,p=self.a_mat[np.argmax(z[i-1])])
342+
z[i,k] = 1
343+
x[i] = self.rng.multivariate_normal(mean=self.mu_vecs[k],cov=_lambda_mats_inv[k])
344+
return x,z
192345

193-
def save_sample(self):
194-
pass
346+
def save_sample(self,filename,sample_length):
347+
"""Save the generated sample as NumPy ``.npz`` format.
348+
349+
It is saved as a NpzFile with keyword: \"x\", \"z\".
350+
351+
Parameters
352+
----------
353+
filename : str
354+
The filename to which the sample is saved.
355+
``.npz`` will be appended if it isn't there.
356+
sample_length : int
357+
A positive integer
358+
359+
See Also
360+
--------
361+
numpy.savez_compressed
362+
"""
363+
x,z=self.gen_sample(sample_length)
364+
np.savez_compressed(filename,x=x,z=z)
195365

196-
def visualize_model(self):
197-
pass
366+
def visualize_model(self,sample_length=200):
367+
"""Visualize the stochastic data generative model and generated samples.
198368
369+
Parameters
370+
----------
371+
sample_length : int, optional
372+
A positive integer, by default 100
373+
374+
Examples
375+
--------
376+
>>> from bayesml import hiddenmarkovnormal
377+
>>> import numpy as np
378+
>>> model = hiddenmarkovnormal.GenModel(
379+
c_num_classes=2,
380+
c_degree=1,
381+
mu_vecs=np.array([[5],[-5]]),
382+
a_mat=np.array([[0.95,0.05],[0.1,0.9]]))
383+
>>> model.visualize_model()
384+
pi_vec:
385+
[0.5 0.5]
386+
a_mat:
387+
[[0.95 0.05]
388+
[0.1 0.9 ]]
389+
mu_vecs:
390+
[[ 5.]
391+
[-5.]]
392+
lambda_mats:
393+
[[[1.]]
394+
395+
[[1.]]]
396+
397+
.. image:: ./images/hiddenmarkovnormal_example.png
398+
"""
399+
if self.c_degree == 1:
400+
print(f"pi_vec:\n {self.pi_vec}")
401+
print(f"a_mat:\n {self.a_mat}")
402+
print(f"mu_vecs:\n {self.mu_vecs}")
403+
print(f"lambda_mats:\n {self.lambda_mats}")
404+
_lambda_mats_inv = np.linalg.inv(self.lambda_mats)
405+
fig, axes = plt.subplots()
406+
sample, latent_vars = self.gen_sample(sample_length)
407+
408+
change_points = [0]
409+
for i in range(1,sample_length):
410+
if np.any(latent_vars[i-1] != latent_vars[i]):
411+
change_points.append(i)
412+
change_points.append(sample_length)
413+
414+
cm = plt.get_cmap('jet')
415+
for i in range(1,len(change_points)):
416+
axes.axvspan(
417+
change_points[i-1],
418+
change_points[i],
419+
color=cm(
420+
int((np.argmax(latent_vars[change_points[i-1]])
421+
/ (self.c_num_classes-1)) * 255)
422+
),
423+
alpha=0.3,
424+
ls='',
425+
)
426+
axes.plot(np.arange(sample.shape[0]),sample)
427+
axes.set_xlabel("time")
428+
axes.set_ylabel("x")
429+
plt.show()
430+
else:
431+
raise(ParameterFormatError("if c_degree > 1, it is impossible to visualize the model by this function."))
199432

200433
class LearnModel(base.Posterior,base.PredictiveMixin):
201434
def __init__(
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from bayesml import hiddenmarkovnormal
2+
import numpy as np
3+
4+
model = hiddenmarkovnormal.GenModel(3,1)
5+
6+
print(model.get_params())
7+
8+
model.set_params(mu_vecs=np.ones([3,1]))
9+
10+
print(model.get_params())

0 commit comments

Comments
 (0)