// gmm/diag-gmm-test.cc // Copyright 2009-2011 Microsoft Corporation; Georg Stemmer; Jan Silovsky; // Saarland University // See ../../COPYING for clarification regarding multiple authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED // WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, // MERCHANTABLITY OR NON-INFRINGEMENT. // See the Apache 2 License for the specific language governing permissions and // limitations under the License. #include "gmm/diag-gmm.h" #include "gmm/mle-diag-gmm.h" #include "util/kaldi-io.h" namespace kaldi { void InitRandomGmm(DiagGmm *gmm_in) { int32 num_gauss = 10 + Rand() % 5; int32 dim = 10 + Rand() % 10; DiagGmm &gmm(*gmm_in); gmm.Resize(num_gauss, dim); Matrix inv_vars(num_gauss, dim), means(num_gauss, dim); Vector weights(num_gauss); for (int32 i = 0; i < num_gauss; i++) { for (int32 j = 0; j < dim; j++) { inv_vars(i, j) = Exp(RandGauss() * (1.0 / (1 + j))); means(i, j) = RandGauss() * (1.0 / (1 + j)); } weights(i) = Exp(RandGauss()); } weights.Scale(1.0 / weights.Sum()); gmm.SetWeights(weights); gmm.SetInvVarsAndMeans(inv_vars, means); gmm.Perturb(0.5 * RandUniform()); gmm.ComputeGconsts(); // this is unnecessary; computed in Perturb } // This tests the Generate function and also the HMM-update. // it relies on some statistical ideas related to the Aikake // criterion. void UnitTestDiagGmmGenerate() { DiagGmm gmm; InitRandomGmm(&gmm); int32 dim = gmm.Dim(); int32 npoints = 100 * gmm.NumGauss(); Matrix rand_points(npoints, dim); for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); gmm.Generate(&row); } int32 niters = 15; BaseFloat objf_change_tot = 0.0, objf_change, count; for (int32 j = 0; j < niters; j++) { MleDiagGmmOptions opts; AccumDiagGmm stats(gmm, kGmmAll); // all update flags. for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); stats.AccumulateFromDiag(gmm, row, 1.0); } MleDiagGmmUpdate(opts, stats, kGmmAll, &gmm, &objf_change, &count); objf_change_tot += objf_change; } AssertEqual(count, npoints, 1e-6); int32 num_params = gmm.NumGauss() * (gmm.Dim()*2 + 1); BaseFloat expected_objf_change = 0.5 * num_params; KALDI_LOG << "Expected objf change is: not much more than " << expected_objf_change <<", seen: " << objf_change_tot; KALDI_ASSERT(objf_change_tot < 2.0 * expected_objf_change); // way too much. // This test relies on statistical laws and if it fails it does not // *necessarily* mean that something is wrong. } void UnitTestDiagGmm() { // random dimension of the gmm size_t dim = 1 + kaldi::RandInt(0, 5); // random number of mixtures size_t nMix = 1 + kaldi::RandInt(0, 5); std::cout << "Testing NumGauss: " << nMix << ", " << "Dim: " << dim << '\n'; // generate random feature vector and // random mean and variance vectors Vector feat(dim), weights(nMix), loglikes(nMix); Matrix means(nMix, dim), vars(nMix, dim), invvars(nMix, dim); float loglike = 0.0; for (size_t d = 0; d < dim; d++) { feat(d) = kaldi::RandGauss(); } float tot_weight = 0.0; for (size_t m = 0; m < nMix; m++) { weights(m) = kaldi::RandUniform(); for (size_t d= 0; d < dim; d++) { means(m, d) = kaldi::RandGauss(); vars(m, d) = Exp(kaldi::RandGauss()) + 1e-5; } tot_weight += weights(m); } // normalize weights for (size_t m = 0; m < nMix; m++) { weights(m) /= tot_weight; for (size_t d= 0; d < dim; d++) { loglikes(m) += -0.5 * (M_LOG_2PI + Log(vars(m, d)) + (feat(d) - means(m, d)) * (feat(d) - means(m, d)) / vars(m, d)); } loglikes(m) += Log(weights(m)); } loglike = loglikes.LogSumExp(); // new GMM DiagGmm *gmm = new DiagGmm(); gmm->Resize(nMix, dim); invvars.CopyFromMat(vars); invvars.InvertElements(); gmm->SetWeights(weights); gmm->SetInvVarsAndMeans(invvars, means); gmm->ComputeGconsts(); Vector posterior1(nMix); float loglike1 = gmm->ComponentPosteriors(feat, &posterior1); std::cout << "LogLike: " << loglike << '\n'; std::cout << "LogLike1: " << loglike1 << '\n'; AssertEqual(loglike, loglike1, 0.01); AssertEqual(1.0, posterior1.Sum(), 0.01); { // Test various accessors / mutators Vector weights_bak(nMix); Matrix means_bak(nMix, dim); Matrix invvars_bak(nMix, dim); weights_bak.CopyFromVec(gmm->weights()); gmm->GetMeans(&means_bak); gmm->GetVars(&invvars_bak); // get vars invvars_bak.InvertElements(); // compute invvars // set all params one-by-one to new model DiagGmm gmm2; gmm2.Resize(gmm->NumGauss(), gmm->Dim()); gmm2.SetWeights(weights_bak); gmm2.SetMeans(means_bak); gmm2.SetInvVars(invvars_bak); gmm2.ComputeGconsts(); BaseFloat loglike_gmm2 = gmm2.LogLikelihood(feat); AssertEqual(loglike1, loglike_gmm2); { Vector loglikes; gmm2.LogLikelihoods(feat, &loglikes); AssertEqual(loglikes.LogSumExp(), loglike_gmm2); } { std::vector indices; for (int32 i = 0; i < gmm2.NumGauss(); i++) indices.push_back(i); Vector loglikes; gmm2.LogLikelihoodsPreselect(feat, indices, &loglikes); AssertEqual(loglikes.LogSumExp(), loglike_gmm2); } // single component mean accessor + mutator DiagGmm gmm3; gmm3.Resize(gmm->NumGauss(), gmm->Dim()); gmm3.SetWeights(weights_bak); means_bak.SetZero(); for (size_t i = 0; i < nMix; i++) { SubVector row(means_bak, i); gmm->GetComponentMean(i, &row); gmm3.SetComponentMean(i, row); } gmm3.SetInvVars(invvars_bak); gmm3.ComputeGconsts(); BaseFloat loglike_gmm3 = gmm3.LogLikelihood(feat); AssertEqual(loglike1, loglike_gmm3, 0.01); } // Test various accessors / mutators end // First, non-binary write. gmm->Write(Output("tmpf", false).Stream(), false); delete gmm; { bool binary_in; DiagGmm *gmm2 = new DiagGmm(); Input ki("tmpf", &binary_in); gmm2->Read(ki.Stream(), binary_in); float loglike4 = gmm2->ComponentPosteriors(feat, &posterior1); AssertEqual(loglike, loglike4, 0.01); // binary write gmm2->Write(Output("tmpfb", true).Stream(), true); delete gmm2; // binary read DiagGmm *gmm3; gmm3 = new DiagGmm(); Input ki2("tmpfb", &binary_in); gmm3->Read(ki2.Stream(), binary_in); float loglike5 = gmm3->ComponentPosteriors(feat, &posterior1); AssertEqual(loglike, loglike5, 0.01); delete gmm3; } { // split and merge test for 1 component GMM (doesn't test the merge crit.) DiagGmm gmm1; Vector weights1(1); Matrix means1(1, dim), vars1(1, dim), invvars1(1, dim); weights1(0) = 1.0; means1.CopyFromMat(means.Range(0, 1, 0, dim)); vars1.CopyFromMat(vars.Range(0, 1, 0, dim)); invvars1.CopyFromMat(vars1); invvars1.InvertElements(); gmm1.Resize(1, dim); gmm1.SetWeights(weights1); gmm1.SetInvVarsAndMeans(invvars1, means1); gmm1.ComputeGconsts(); DiagGmm gmm2; gmm2.CopyFromDiagGmm(gmm1); gmm2.Split(2, 0.001); gmm2.Merge(1); float loglike1 = gmm1.LogLikelihood(feat); float loglike2 = gmm2.LogLikelihood(feat); AssertEqual(loglike1, loglike2, 0.01); } { // split and merge test for 1 component GMM, this time using K-means algorithm. DiagGmm gmm1; Vector weights1(1); Matrix means1(1, dim), vars1(1, dim), invvars1(1, dim); weights1(0) = 1.0; means1.CopyFromMat(means.Range(0, 1, 0, dim)); vars1.CopyFromMat(vars.Range(0, 1, 0, dim)); invvars1.CopyFromMat(vars1); invvars1.InvertElements(); gmm1.Resize(1, dim); gmm1.SetWeights(weights1); gmm1.SetInvVarsAndMeans(invvars1, means1); gmm1.ComputeGconsts(); DiagGmm gmm2; gmm2.CopyFromDiagGmm(gmm1); gmm2.Split(2, 0.001); gmm2.MergeKmeans(1); float loglike1 = gmm1.LogLikelihood(feat); float loglike2 = gmm2.LogLikelihood(feat); AssertEqual(loglike1, loglike2, 0.01); } { // Duplicate Gaussians using initializer that takes a vector, and // check like is unchanged. DiagGmm gmm1; Vector weights1(1); Matrix means1(1, dim), vars1(1, dim), invvars1(1, dim); weights1(0) = 1.0; means1.CopyFromMat(means.Range(0, 1, 0, dim)); vars1.CopyFromMat(vars.Range(0, 1, 0, dim)); invvars1.CopyFromMat(vars1); invvars1.InvertElements(); gmm1.Resize(1, dim); gmm1.SetWeights(weights1); gmm1.SetInvVarsAndMeans(invvars1, means1); gmm1.ComputeGconsts(); std::vector > vec; vec.push_back(std::make_pair(static_cast(0.4), (const DiagGmm*)(&gmm1))); vec.push_back(std::make_pair(static_cast(0.6), (const DiagGmm*)(&gmm1))); DiagGmm gmm2(vec); float loglike1 = gmm1.LogLikelihood(feat); float loglike2 = gmm2.LogLikelihood(feat); AssertEqual(loglike1, loglike2, 0.01); } unlink("tmpf"); unlink("tmpfb"); } } // end namespace kaldi int main() { // repeat the test ten times for (int i = 0; i < 2; i++) { kaldi::UnitTestDiagGmm(); kaldi::UnitTestDiagGmmGenerate(); } std::cout << "Test OK.\n"; }