// transform/fmllr-diag-gmm-test.cc // Copyright 2009-2011 Microsoft Corporation // 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 "util/common-utils.h" #include "gmm/diag-gmm.h" #include "transform/fmllr-diag-gmm.h" namespace kaldi { void InitRandomGmm (DiagGmm *gmm_in) { int32 num_gauss = 5 + rand () % 4; 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.ComputeGconsts(); } // This test is statistical and relies on some identities // related to the Aikake criterion. void UnitTestFmllrDiagGmm() { using namespace kaldi; DiagGmm gmm; InitRandomGmm(&gmm); int32 dim = gmm.Dim(); int32 npoints = dim*(dim+1)*5; Matrix rand_points(npoints, dim); for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); gmm.Generate(&row); } Matrix cur_xform(dim, dim+1); cur_xform.SetUnit(); // set diag to unit. int32 niters = 5; BaseFloat objf_change_tot = 0.0, objf_change, count; for (int32 j = 0; j < niters; j++) { FmllrOptions opts; FmllrDiagGmmAccs stats(dim, j % 2 == 0 ? opts : FmllrOptions()); for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); if (j == 0) { // split this case off to exercise more of the code. stats.AccumulateForGmm(gmm, row, 1.0); } else { Vector xformed_row(row); ApplyAffineTransform(cur_xform, &xformed_row); Vector posteriors(gmm.NumGauss()); gmm.ComponentPosteriors(xformed_row, &posteriors); stats.AccumulateFromPosteriors(gmm, row, posteriors); } } stats.Update(opts, &cur_xform, &objf_change, &count); { // Test for ApplyFeatureTransformToStats: BaseFloat objf_change_tmp, count_tmp; ApplyFeatureTransformToStats(cur_xform, &stats); Matrix mat(dim, dim+1); mat.SetUnit(); stats.Update(opts, &mat, &objf_change_tmp, &count_tmp); // After we apply this transform to the stats, there should // be nothing to gain from further transforming the data. KALDI_ASSERT(objf_change_tmp/count_tmp < 0.01); } KALDI_LOG << "Objf change on iter " << j << " is " << objf_change; objf_change_tot += objf_change; } KALDI_ASSERT(ApproxEqual(count, npoints)); int32 num_params = dim*(dim+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); // or way too much. // This test relies on statistical laws and if it fails it does not *necessarily* // mean that something is wrong. } // This is a test for the diagonal update and also of ApplyModelTransformToStats(). void UnitTestFmllrDiagGmmDiagonal() { using namespace kaldi; DiagGmm gmm; InitRandomGmm(&gmm); int32 dim = gmm.Dim(); int32 npoints = dim*(dim+1)*5; Matrix rand_points(npoints, dim); for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); gmm.Generate(&row); } Matrix cur_xform(dim, dim+1); cur_xform.SetUnit(); // set diag to unit. int32 niters = 2; BaseFloat objf_change_tot = 0.0, objf_change, count; FmllrOptions opts; opts.update_type = "diag"; for (int32 j = 0; j < niters; j++) { FmllrDiagGmmAccs stats(dim, j % 2 == 0 ? opts : FmllrOptions()); for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); if (j == 0) { // split this case off to exercise more of the code. stats.AccumulateForGmm(gmm, row, 1.0); } else { Vector xformed_row(row); ApplyAffineTransform(cur_xform, &xformed_row); Vector posteriors(gmm.NumGauss()); gmm.ComponentPosteriors(xformed_row, &posteriors); stats.AccumulateFromPosteriors(gmm, row, posteriors); } } stats.Update(opts, &cur_xform, &objf_change, &count); { // Test for ApplyModelTransformToStats: BaseFloat objf_change_tmp, count_tmp; ApplyModelTransformToStats(cur_xform, &stats); Matrix mat(dim, dim+1); mat.SetUnit(); stats.Update(opts, &mat, &objf_change_tmp, &count_tmp); // After we apply this transform to the stats, there should // be nothing to gain from further transforming the data. KALDI_ASSERT(objf_change_tmp/count_tmp < 0.01); } KALDI_LOG << "Objf change on iter " << j << " is " << objf_change; objf_change_tot += objf_change; } KALDI_ASSERT(ApproxEqual(count, npoints)); int32 num_params = dim*2; 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); // or way too much. // This test relies on statistical laws and if it fails it does not *necessarily* // mean that something is wrong. } // This is a test for the offset-only update and also of ApplyModelTransformToStats(). void UnitTestFmllrDiagGmmOffset() { using namespace kaldi; DiagGmm gmm; InitRandomGmm(&gmm); int32 dim = gmm.Dim(); int32 npoints = dim*(dim+1)*5; Matrix rand_points(npoints, dim); for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); gmm.Generate(&row); } Matrix cur_xform(dim, dim+1); cur_xform.SetUnit(); // set diag to unit. int32 niters = 2; BaseFloat objf_change_tot = 0.0, objf_change, count; FmllrOptions opts; opts.update_type = "offset"; for (int32 j = 0; j < niters; j++) { FmllrDiagGmmAccs stats(dim, j % 2 == 0 ? opts : FmllrOptions()); for (int32 i = 0; i < npoints; i++) { SubVector row(rand_points, i); if (j == 0) { // split this case off to exercise more of the code. stats.AccumulateForGmm(gmm, row, 1.0); } else { Vector xformed_row(row); ApplyAffineTransform(cur_xform, &xformed_row); Vector posteriors(gmm.NumGauss()); gmm.ComponentPosteriors(xformed_row, &posteriors); stats.AccumulateFromPosteriors(gmm, row, posteriors); } } stats.Update(opts, &cur_xform, &objf_change, &count); { // Test for ApplyModelTransformToStats: BaseFloat objf_change_tmp, count_tmp; ApplyModelTransformToStats(cur_xform, &stats); Matrix mat(dim, dim+1); mat.SetUnit(); stats.Update(opts, &mat, &objf_change_tmp, &count_tmp); // After we apply this transform to the stats, there should // be nothing to gain from further transforming the data. KALDI_ASSERT(objf_change_tmp/count_tmp < 0.01); } KALDI_LOG << "Objf change on iter " << j << " is " << objf_change; objf_change_tot += objf_change; } KALDI_ASSERT(ApproxEqual(count, npoints)); int32 num_params = dim; 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); // or way too much. // This test relies on statistical laws and if it fails it does not *necessarily* // mean that something is wrong. } } // namespace kaldi ends here int main() { for (int i = 0; i < 2; i++) { // did more iterations when first testing... kaldi::UnitTestFmllrDiagGmmOffset(); kaldi::UnitTestFmllrDiagGmmDiagonal(); kaldi::UnitTestFmllrDiagGmm(); } std::cout << "Test OK.\n"; }