@@ -482,8 +482,8 @@ def __init__(
482
482
self .h0_w_mats = np .tile (np .identity (self .degree ),[self .num_classes ,1 ,1 ])
483
483
self .h0_w_mats_inv = np .linalg .inv (self .h0_w_mats )
484
484
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 )
487
487
488
488
# hn_params
489
489
self .hn_alpha_vec = np .empty ([self .num_classes ])
@@ -585,15 +585,7 @@ def set_h0_params(
585
585
self .h0_w_mats [:] = h0_w_mats
586
586
self .h0_w_mats_inv [:] = np .linalg .inv (self .h0_w_mats )
587
587
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 ()
597
589
self .reset_hn_params ()
598
590
599
591
def get_h0_params (self ):
@@ -669,6 +661,9 @@ def set_hn_params(
669
661
self .hn_w_mats [:] = hn_w_mats
670
662
self .hn_w_mats_inv [:] = np .linalg .inv (self .hn_w_mats )
671
663
664
+ self ._calc_q_pi_char ()
665
+ self ._calc_q_lambda_char ()
666
+
672
667
self .calc_pred_dist ()
673
668
674
669
def get_hn_params (self ):
@@ -707,6 +702,16 @@ def reset_hn_params(self):
707
702
708
703
self .calc_pred_dist ()
709
704
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
+
710
715
def overwrite_h0_params (self ):
711
716
"""Overwrite the initial values of the hyperparameters of the posterior distribution by the learned values.
712
717
@@ -720,7 +725,8 @@ def overwrite_h0_params(self):
720
725
self .h0_w_mats [:] = self .hn_w_mats
721
726
self .h0_w_mats_inv = np .linalg .inv (self .h0_w_mats )
722
727
723
- self .calc_pred_dist ()
728
+ self .calc_prior_char ()
729
+ self .reset_hn_params ()
724
730
725
731
def calc_vl (self ):
726
732
# E[ln p(X|Z,mu,Lambda)]
@@ -740,7 +746,7 @@ def calc_vl(self):
740
746
self ._vl_p_z = (self .ns * self ._e_ln_pi_vec ).sum ()
741
747
742
748
# 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 ()
744
750
745
751
# E[ln p(mu,Lambda)]
746
752
self ._vl_p_mu_lambda = np .sum (
@@ -749,7 +755,7 @@ def calc_vl(self):
749
755
- self .h0_kappas * ((self .hn_m_vecs - self .h0_m_vecs )[:,np .newaxis ,:]
750
756
@ self ._e_lambda_mats
751
757
@ (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
753
759
+ (self .h0_nus - self .degree ) * self ._e_ln_lambda_dets
754
760
- np .sum (self .h0_w_mats_inv * self ._e_lambda_mats ,axis = (1 ,2 ))
755
761
) / 2.0
@@ -838,7 +844,7 @@ def _update_q_z(self,x):
838
844
self ._calc_n_x_bar_s (x )
839
845
840
846
def _init_subsampling (self ,x ):
841
- _size = int (np .sqrt (x .shape [0 ])) + 1
847
+ _size = int (np .sqrt (x .shape [0 ]))
842
848
for k in range (self .num_classes ):
843
849
_subsample = self .rng .choice (x ,size = _size ,replace = False ,axis = 0 ,shuffle = False )
844
850
self .hn_m_vecs [k ] = _subsample .sum (axis = 0 ) / _size
@@ -850,7 +856,14 @@ def _init_subsampling(self,x):
850
856
self ._calc_q_pi_char ()
851
857
self ._calc_q_lambda_char ()
852
858
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
+ ):
854
867
"""Update the hyperparameters of the posterior distribution using traning data.
855
868
856
869
Parameters
@@ -863,9 +876,14 @@ def update_posterior(self,x,max_itr=100,num_init=10,tolerance=1.0E-8,init_type='
863
876
number of initializations, by default 10
864
877
tolerance : float, optional
865
878
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
866
884
"""
867
885
_check .float_vecs (x ,'x' ,DataFormatError )
868
- if self . degree > 1 and x .shape [- 1 ] != self .degree :
886
+ if x .shape [- 1 ] != self .degree :
869
887
raise (DataFormatError (
870
888
"x.shape[-1] must be self.degree: "
871
889
+ 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='
930
948
self ._calc_q_lambda_char ()
931
949
self ._update_q_z (x )
932
950
933
-
934
951
def estimate_params (self ,loss = "squared" ):
935
952
"""Estimate the parameter of the stochastic data generative model under the given criterion.
936
953
@@ -1135,7 +1152,15 @@ def make_prediction(self,loss="squared"):
1135
1152
raise (CriteriaError (f"loss={ loss } is unsupported. "
1136
1153
+ "This function supports \" squared\" and \" 0-1\" ." ))
1137
1154
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
+ ):
1139
1164
"""Predict a new data point and update the posterior sequentially.
1140
1165
1141
1166
h0_params will be overwritten by current hn_params
@@ -1148,6 +1173,17 @@ def pred_and_update(self,x,loss="squared"):
1148
1173
loss : str, optional
1149
1174
Loss function underlying the Bayes risk function, by default \" squared\" .
1150
1175
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.
1151
1187
1152
1188
Returns
1153
1189
-------
@@ -1160,5 +1196,98 @@ def pred_and_update(self,x,loss="squared"):
1160
1196
self .calc_pred_dist ()
1161
1197
prediction = self .make_prediction (loss = loss )
1162
1198
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
+ )
1164
1206
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
0 commit comments