// transform/fmpe-test.cc // Copyright 2012 Johns Hopkins University (Author: Daniel Povey) // 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 "gmm/diag-gmm-normal.h" #include "gmm/model-test-common.h" #include "transform/fmpe.h" namespace kaldi { // Compute derivative of GMM log-likelihood w.r.t. features. // Note: this code copied from gmm-get-feat-deriv.cc; had // to simplify a bit. void GetFeatDeriv(const DiagGmm &gmm, const Matrix &feats, Matrix *deriv) { deriv->Resize(feats.NumRows(), feats.NumCols()); Vector gauss_posteriors; Vector temp_vec(feats.NumCols()); for (int32 i = 0; i < feats.NumRows(); i++) { SubVector this_feat(feats, i); SubVector this_deriv(*deriv, i); gmm.ComponentPosteriors(this_feat, &gauss_posteriors); BaseFloat weight = 1.0; gauss_posteriors.Scale(weight); // The next line does: to i'th row of deriv, add // means_invvars^T * gauss_posteriors, // where each row of means_invvars is the mean times // diagonal inverse covariance... after transposing, // this becomes a weighted of these rows, weighted by // the posteriors. This comes from the term // feat^T * inv_var * mean // in the objective function. this_deriv.AddMatVec(1.0, gmm.means_invvars(), kTrans, gauss_posteriors, 1.0); // next line does temp_vec == inv_vars^T * gauss_posteriors, // which sets temp_vec to a weighted sum of the inv_vars, // weighed by Gaussian posterior. temp_vec.AddMatVec(1.0, gmm.inv_vars(), kTrans, gauss_posteriors, 0.0); // Add to the derivative, -(this_feat .* temp_vec), // which is the term that comes from the -0.5 * inv_var^T feat_sq, // in the objective function (where inv_var is a vector, and feat_sq // is a vector of squares of the feature values). this_deriv.AddVecVec(-1.0, this_feat, temp_vec, 1.0); } } // Gets total log-likelihood, summed over all frames. BaseFloat GetGmmLike(const DiagGmm &gmm, const Matrix &feats) { BaseFloat ans = 0.0; for (int32 i = 0; i < feats.NumRows(); i++) ans += gmm.LogLikelihood(feats.Row(i)); return ans; } void TestFmpe() { int32 dim = 10 + (Rand() % 10); int32 num_comp = 10 + (Rand() % 10); DiagGmm gmm; unittest::InitRandDiagGmm(dim, num_comp, &gmm); int32 num_frames = 20; Matrix feats(num_frames, dim); for (int32 i = 0; i < num_frames; i++) for (int32 j = 0; j < dim; j++) feats(i, j) = RandGauss(); FmpeOptions opts; // Default. { Fmpe fmpe(gmm, opts); { bool binary = (Rand() % 2 == 1); Output ko("tmpf", binary); fmpe.Write(ko.Stream(), binary); } } Fmpe fmpe(gmm, opts); { bool binary_in; Input ki("tmpf", &binary_in); fmpe.Read(ki.Stream(), binary_in); } // We'll first be testing that the feature derivative is // accurate, by measuring a small random offset in feature space. { Matrix deriv; Matrix random_offset(feats.NumRows(), feats.NumCols()); for (int32 i = 0; i < feats.NumRows(); i++) for (int32 j = 0; j < feats.NumCols(); j++) random_offset(i, j) = 1.0e-03 * RandGauss(); BaseFloat like_before = GetGmmLike(gmm, feats); feats.AddMat(1.0, random_offset); BaseFloat like_after = GetGmmLike(gmm, feats); feats.AddMat(-1.0, random_offset); // undo the change. GetFeatDeriv(gmm, feats, &deriv); BaseFloat change1 = like_after - like_before, change2 = TraceMatMat(random_offset, deriv, kTrans); KALDI_LOG << "Random offset led to like change " << change1 << " (manually), and " << change2 << " (derivative)"; // note: not making this threshold smaller, as don't want // spurious failures. Seems to be OK though. KALDI_ASSERT( fabs(change1-change2) < 0.15*fabs(change1+change2)); } std::vector > gselect(feats.NumRows()); // make it have all Gaussians... for (int32 i = 0; i < feats.NumRows(); i++) for (int32 j = 0; j < gmm.NumGauss(); j++) gselect[i].push_back(j); Matrix fmpe_offset; // Check that the fMPE feature offset is zero. fmpe.ComputeFeatures(feats, gselect, &fmpe_offset); KALDI_ASSERT(fmpe_offset.IsZero()); // Note: we're just using the ML objective function here. // This is just to make sure the derivatives are all computed // correctly. BaseFloat like_before_update = GetGmmLike(gmm, feats); // Now get stats for update. FmpeStats stats(fmpe); Matrix deriv; GetFeatDeriv(gmm, feats, &deriv); fmpe.AccStats(feats, gselect, deriv, NULL, &stats); FmpeUpdateOptions update_opts; update_opts.learning_rate = 0.001; // so linear assumption is more valid. BaseFloat delta = fmpe.Update(update_opts, stats); fmpe.ComputeFeatures(feats, gselect, &fmpe_offset); feats.AddMat(1.0, fmpe_offset); BaseFloat like_after_update = GetGmmLike(gmm, feats); BaseFloat delta2 = like_after_update - like_before_update; KALDI_LOG << "Change predicted by fMPE Update function is " << delta << ", change computed directly is " << delta2; KALDI_ASSERT(fabs(delta-delta2) < 0.15 * fabs(delta+delta2)); unlink("tmpf"); } } int main() { kaldi::g_kaldi_verbose_level = 5; for (int i = 0; i <= 10; i++) kaldi::TestFmpe(); std::cout << "Test OK.\n"; }