// ivector/ivector-extractor.cc // Copyright 2013 Daniel Povey // 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 #include "ivector/ivector-extractor.h" #include "thread/kaldi-task-sequence.h" namespace kaldi { int32 IvectorExtractor::FeatDim() const { KALDI_ASSERT(!M_.empty()); return M_[0].NumRows(); } int32 IvectorExtractor::IvectorDim() const { KALDI_ASSERT(!M_.empty()); return M_[0].NumCols(); } int32 IvectorExtractor::NumGauss() const { return static_cast(M_.size()); } void IvectorExtractor::GetStats( const MatrixBase &feats, const Posterior &post, IvectorExtractorUtteranceStats *stats) const { typedef std::vector > VecType; int32 num_frames = feats.NumRows(), num_gauss = NumGauss(), feat_dim = FeatDim(); KALDI_ASSERT(feats.NumCols() == feat_dim); KALDI_ASSERT(stats->gamma.Dim() == num_gauss && stats->X.NumCols() == feat_dim); bool update_variance = (!stats->S.empty()); for (int32 t = 0; t < num_frames; t++) { SubVector frame(feats, t); const VecType &this_post(post[t]); SpMatrix outer_prod; if (update_variance) { outer_prod.Resize(feat_dim); outer_prod.AddVec2(1.0, frame); } for (VecType::const_iterator iter = this_post.begin(); iter != this_post.end(); ++iter) { int32 i = iter->first; // Gaussian index. KALDI_ASSERT(i >= 0 && i < num_gauss && "Out-of-range Gaussian (mismatched posteriors?)"); double weight = iter->second; stats->gamma(i) += weight; stats->X.Row(i).AddVec(weight, frame); if (update_variance) stats->S[i].AddSp(weight, outer_prod); } } } // This function basically inverts the input and puts it in the output, but it's // smart numerically. It uses the prior knowledge that the "inverse_floor" can // have no eigenvalues less than one, so it applies that floor (in double // precision) before inverting. This avoids certain numerical problems that can // otherwise occur. // static void IvectorExtractor::InvertWithFlooring(const SpMatrix &inverse_var, SpMatrix *var) { SpMatrix dbl_var(inverse_var); int32 dim = inverse_var.NumRows(); Vector s(dim); Matrix P(dim, dim); // Solve the symmetric eigenvalue problem, inverse_var = P diag(s) P^T. inverse_var.Eig(&s, &P); s.ApplyFloor(1.0); s.InvertElements(); var->AddMat2Vec(1.0, P, kNoTrans, s, 0.0); } void IvectorExtractor::GetIvectorDistribution( const IvectorExtractorUtteranceStats &utt_stats, VectorBase *mean, SpMatrix *var) const { if (!IvectorDependentWeights()) { Vector linear(IvectorDim()); SpMatrix quadratic(IvectorDim()); GetIvectorDistMean(utt_stats, &linear, &quadratic); GetIvectorDistPrior(utt_stats, &linear, &quadratic); if (var != NULL) { var->CopyFromSp(quadratic); var->Invert(); // now it's a variance. // mean of distribution = quadratic^{-1} * linear... mean->AddSpVec(1.0, *var, linear, 0.0); } else { quadratic.Invert(); mean->AddSpVec(1.0, quadratic, linear, 0.0); } } else { Vector linear(IvectorDim()); SpMatrix quadratic(IvectorDim()); GetIvectorDistMean(utt_stats, &linear, &quadratic); GetIvectorDistPrior(utt_stats, &linear, &quadratic); // At this point, "linear" and "quadratic" contain // the mean and prior-related terms, and we avoid // recomputing those. Vector cur_mean(IvectorDim()); SpMatrix quadratic_inv(IvectorDim()); InvertWithFlooring(quadratic, &quadratic_inv); cur_mean.AddSpVec(1.0, quadratic_inv, linear, 0.0); KALDI_VLOG(3) << "Trace of quadratic is " << quadratic.Trace() << ", condition is " << quadratic.Cond(); KALDI_VLOG(3) << "Trace of quadratic_inv is " << quadratic_inv.Trace() << ", condition is " << quadratic_inv.Cond(); // The loop is finding successively better approximation points // for the quadratic expansion of the weights. int32 num_iters = 4; double change_threshold = 0.1; // If the iVector changes by less than // this (in 2-norm), we abort early. for (int32 iter = 0; iter < num_iters; iter++) { if (GetVerboseLevel() >= 3) { KALDI_VLOG(3) << "Auxf on iter " << iter << " is " << GetAuxf(utt_stats, cur_mean, &quadratic_inv); int32 show_dim = 5; if (show_dim > cur_mean.Dim()) show_dim = cur_mean.Dim(); KALDI_VLOG(3) << "Current distribution mean is " << cur_mean.Range(0, show_dim) << "... " << ", var trace is " << quadratic_inv.Trace(); } Vector this_linear(linear); SpMatrix this_quadratic(quadratic); GetIvectorDistWeight(utt_stats, cur_mean, &this_linear, &this_quadratic); InvertWithFlooring(this_quadratic, &quadratic_inv); Vector mean_diff(cur_mean); cur_mean.AddSpVec(1.0, quadratic_inv, this_linear, 0.0); mean_diff.AddVec(-1.0, cur_mean); double change = mean_diff.Norm(2.0); KALDI_VLOG(2) << "On iter " << iter << ", iVector changed by " << change; if (change < change_threshold) break; } mean->CopyFromVec(cur_mean); if (var != NULL) var->CopyFromSp(quadratic_inv); } } IvectorExtractor::IvectorExtractor( const IvectorExtractorOptions &opts, const FullGmm &fgmm) { KALDI_ASSERT(opts.ivector_dim > 0); Sigma_inv_.resize(fgmm.NumGauss()); for (int32 i = 0; i < fgmm.NumGauss(); i++) { const SpMatrix &inv_var = fgmm.inv_covars()[i]; Sigma_inv_[i].Resize(inv_var.NumRows()); Sigma_inv_[i].CopyFromSp(inv_var); } Matrix gmm_means; fgmm.GetMeans(&gmm_means); KALDI_ASSERT(!Sigma_inv_.empty()); int32 feature_dim = Sigma_inv_[0].NumRows(), num_gauss = Sigma_inv_.size(); ivector_offset_ = 100.0; // hardwired for now. Must be nonzero. gmm_means.Scale(1.0 / ivector_offset_); M_.resize(num_gauss); for (int32 i = 0; i < num_gauss; i++) { M_[i].Resize(feature_dim, opts.ivector_dim); M_[i].SetRandn(); M_[i].CopyColFromVec(gmm_means.Row(i), 0); } if (opts.use_weights) { // will regress the log-weights on the iVector. w_.Resize(num_gauss, opts.ivector_dim); } else { w_vec_.Resize(fgmm.NumGauss()); w_vec_.CopyFromVec(fgmm.weights()); } ComputeDerivedVars(); } void IvectorExtractor::ComputeDerivedVars() { KALDI_LOG << "Computing derived variables for iVector extractor"; gconsts_.Resize(NumGauss()); for (int32 i = 0; i < NumGauss(); i++) { double var_logdet = -Sigma_inv_[i].LogPosDefDet(); gconsts_(i) = -0.5 * (var_logdet + FeatDim() * M_LOG_2PI); // the gconsts don't contain any weight-related terms. } U_.Resize(NumGauss(), IvectorDim() * (IvectorDim() + 1) / 2); SpMatrix temp_U(IvectorDim()); for (int32 i = 0; i < NumGauss(); i++) { // temp_U = M_i^T Sigma_i^{-1} M_i temp_U.AddMat2Sp(1.0, M_[i], kTrans, Sigma_inv_[i], 0.0); SubVector temp_U_vec(temp_U.Data(), IvectorDim() * (IvectorDim() + 1) / 2); U_.Row(i).CopyFromVec(temp_U_vec); } KALDI_LOG << "Done."; } void IvectorExtractor::GetIvectorDistWeight( const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &mean, VectorBase *linear, SpMatrix *quadratic) const { // If there is no w_, then weights do not depend on the iVector // and the weights contribute nothing to the distribution. if (!IvectorDependentWeights()) return; Vector logw_unnorm(NumGauss()); logw_unnorm.AddMatVec(1.0, w_, kNoTrans, mean, 0.0); Vector w(logw_unnorm); w.ApplySoftMax(); // now w is the weights. // See eq.58 in SGMM paper // http://www.sciencedirect.com/science/article/pii/S088523081000063X // linear_coeff(i) = \gamma_{jmi} - \gamma_{jm} \hat{w}_{jmi} + \max(\gamma_{jmi}, \gamma_{jm} \hat{w}_{jmi} \hat{\w}_i \v_{jm} // here \v_{jm} corresponds to the iVector. Ignore the j,m indices. Vector linear_coeff(NumGauss()); Vector quadratic_coeff(NumGauss()); double gamma = utt_stats.gamma.Sum(); for (int32 i = 0; i < NumGauss(); i++) { double gamma_i = utt_stats.gamma(i); double max_term = std::max(gamma_i, gamma * w(i)); linear_coeff(i) = gamma_i - gamma * w(i) + max_term * logw_unnorm(i); quadratic_coeff(i) = max_term; } linear->AddMatVec(1.0, w_, kTrans, linear_coeff, 1.0); // *quadratic += \sum_i quadratic_coeff(i) w_i w_i^T, where w_i is // i'th row of w_. quadratic->AddMat2Vec(1.0, w_, kTrans, quadratic_coeff, 1.0); } void IvectorExtractor::GetIvectorDistMean( const IvectorExtractorUtteranceStats &utt_stats, VectorBase *linear, SpMatrix *quadratic) const { Vector temp(FeatDim()); int32 I = NumGauss(); for (int32 i = 0; i < I; i++) { double gamma = utt_stats.gamma(i); if (gamma != 0.0) { Vector x(utt_stats.X.Row(i)); // == \gamma(i) \m_i temp.AddSpVec(1.0 / gamma, Sigma_inv_[i], x, 0.0); // now temp = Sigma_i^{-1} \m_i. // next line: a += \gamma_i \M_i^T \Sigma_i^{-1} \m_i linear->AddMatVec(gamma, M_[i], kTrans, temp, 1.0); } } SubVector q_vec(quadratic->Data(), IvectorDim()*(IvectorDim()+1)/2); q_vec.AddMatVec(1.0, U_, kTrans, Vector(utt_stats.gamma), 1.0); } void IvectorExtractor::GetIvectorDistPrior( const IvectorExtractorUtteranceStats &utt_stats, VectorBase *linear, SpMatrix *quadratic) const { (*linear)(0) += ivector_offset_; // the zero'th dimension has an offset mean. /// The inverse-variance for the prior is the unit matrix. for (int32 d = 0; d < IvectorDim(); d++) (*quadratic)(d, d) += 1.0; } double IvectorExtractor::GetAcousticAuxfWeight( const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &mean, const SpMatrix *var) const { if (!IvectorDependentWeights()) { // Not using the weight-projection matrices. Vector log_w_vec(w_vec_); log_w_vec.ApplyLog(); return VecVec(log_w_vec, utt_stats.gamma); } else { Vector w(NumGauss()); w.AddMatVec(1.0, w_, kNoTrans, mean, 0.0); // now w is unnormalized // log-weights. double lse = w.LogSumExp(); w.Add(-lse); // Normalize so log-weights sum to one. // "ans" below is the point-value of the weight auxf, without // considering the variance. At the moment, "w" contains // the normalized log weights. double ans = VecVec(w, utt_stats.gamma); w.ApplyExp(); // now w is the weights. if (var == NULL) { return ans; } else { // Below, "Jacobian" will be the derivative d(log_w) / d(ivector) // = (I - w w^T) W, where W (w_ in the code) is the projection matrix // from iVector space to unnormalized log-weights, and w is the normalized // weight values at the current point. Matrix Jacobian(w_); Vector WTw(IvectorDim()); // W^T w WTw.AddMatVec(1.0, w_, kTrans, w, 0.0); Jacobian.AddVecVec(1.0, w, WTw); // Jacobian += (w (W^T w)^T = w^T w W) // the matrix S is the negated 2nd derivative of the objf w.r.t. the iVector \x. SpMatrix S(IvectorDim()); S.AddMat2Vec(1.0, Jacobian, kTrans, Vector(utt_stats.gamma), 0.0); ans += -0.5 * TraceSpSp(S, *var); return ans; } } } double IvectorExtractor::GetAuxf(const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &mean, const SpMatrix *var) const { double acoustic_auxf = GetAcousticAuxf(utt_stats, mean, var), prior_auxf = GetPriorAuxf(mean, var), num_frames = utt_stats.gamma.Sum(); KALDI_VLOG(3) << "Acoustic auxf is " << (acoustic_auxf/num_frames) << "/frame over " << num_frames << " frames, prior auxf is " << prior_auxf << " = " << (prior_auxf/num_frames) << " per frame."; return acoustic_auxf + prior_auxf; } /* Get the prior-related part of the auxiliary function. Suppose the ivector is x, the prior distribution is p(x), the likelihood of the data given x (itself an auxiliary function) is q(x) and the prior is r(x). In the case where we just have a point ivector x and no p(x), the prior-related term we return will just be q(x). If we have a distribution over x, we define an auxiliary function t(x) = \int p(x) log(q(x)r(x) / p(x)) dx. We separate this into data-dependent and prior parts, where the prior part is \int p(x) log(q(x) / p(x)) dx (which this function returns), and the acoustic part is \int p(x) log(r(x)) dx. Note that the fact that we divide by p(x) in the prior part and not the acoustic part is a bit arbitrary, at least the way we have written it down here; it doesn't matter where we do it, but this way seems more natural. */ double IvectorExtractor::GetPriorAuxf( const VectorBase &mean, const SpMatrix *var) const { KALDI_ASSERT(mean.Dim() == IvectorDim()); Vector offset(mean); offset(0) -= ivector_offset_; // The mean of the prior distribution // may only be nonzero in the first dimension. Now, "offset" is the // offset of ivector from the prior's mean. if (var == NULL) { // The log-determinant of the variance of the prior distribution is one, // since it's the unit matrix. return -0.5 * (VecVec(offset, offset) + IvectorDim()*M_LOG_2PI); } else { // The mean-related part of the answer will be // -0.5 * (VecVec(offset, offset), just like above. // The variance-related part will be // \int p(x) . -0.5 (x^T I x - x^T var^{-1} x + logdet(I) - logdet(var)) dx // and using the fact that x is distributed with variance "var", this is: //= \int p(x) . -0.5 (x^T I x - x^T var^{-1} x + logdet(I) - logdet(var)) dx // = -0.5 ( trace(var I) - trace(var^{-1} var) + 0.0 - logdet(var)) // = -0.5 ( trace(var) - dim(var) - logdet(var)) KALDI_ASSERT(var->NumRows() == IvectorDim()); return -0.5 * (VecVec(offset, offset) + var->Trace() - IvectorDim() - var->LogPosDefDet()); } } /* Gets the acoustic-related part of the auxf. If the distribution over the ivector given by "mean" and "var" is p(x), and the acoustic auxiliary-function given x is r(x), this function returns \int p(x) log r(x) dx */ double IvectorExtractor::GetAcousticAuxf( const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &mean, const SpMatrix *var) const { double weight_auxf = GetAcousticAuxfWeight(utt_stats, mean, var), gconst_auxf = GetAcousticAuxfGconst(utt_stats), mean_auxf = GetAcousticAuxfMean(utt_stats, mean, var), var_auxf = GetAcousticAuxfVariance(utt_stats), T = utt_stats.gamma.Sum(); KALDI_VLOG(3) << "Per frame, auxf is: weight " << (weight_auxf/T) << ", gconst " << (gconst_auxf/T) << ", mean " << (mean_auxf/T) << ", var " << (var_auxf/T) << ", over " << T << " frames."; return weight_auxf + gconst_auxf + mean_auxf + var_auxf; } /* This is the part of the auxf that involves the data means, and we also involve the gconsts here. Let \m_i be the observed data mean vector for the i'th Gaussian (which we get from the utterance-specific stats). Let \mu_i(\x) be the mean of the i'th Gaussian, written as a function of the iVector \x. \int_\x p(\x) (\sum_i \gamma_i -0.5 (\mu(\x) - \m_i)^T \Sigma_i^{-1} (\mu(\x) - \m_i)) d\x To compute this integral we'll first write out the summation as a function of \x. \sum_i -0.5 \gamma_i \m_i^T \Sigma_i^{-1} \m_i + \gamma_i \mu(\x)^T \Sigma_i^{-1} \m_i -0.5 \gamma_i \mu(\x)^T \Sigma_i^{-1} \m_i = \sum_i -0.5 \gamma_i \m_i^T \Sigma_i^{-1} \m_i + \gamma_i \x^T \M_i^T \Sigma_i^{-1} \m_i -0.5 \gamma_i \x^T \M_i^T \Sigma_i^{-1} \x = K + \x^T \a - 0.5 \x^T \B \x, where K = \sum_i -0.5 \gamma_i \m_i^T \Sigma_i^{-1} \m_i \a = \sum_i \gamma_i \M_i^T \Sigma_i^{-1} \m_i \B = \sum_i \gamma_i \U_i (where \U_i = \M_i^T \Sigma_i^{-1} \M_i has been stored previously). Note: the matrix M in the stats actually contains \gamma_i \m_i, i.e. the mean times the count, so we need to modify the definitions above accordingly. */ double IvectorExtractor::GetAcousticAuxfMean( const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &mean, const SpMatrix *var) const { double K = 0.0; Vector a(IvectorDim()), temp(FeatDim()); int32 I = NumGauss(); for (int32 i = 0; i < I; i++) { double gamma = utt_stats.gamma(i); if (gamma != 0.0) { Vector x(utt_stats.X.Row(i)); // == \gamma(i) \m_i temp.AddSpVec(1.0 / gamma, Sigma_inv_[i], x, 0.0); // now temp = Sigma_i^{-1} \m_i. // next line: K += -0.5 \gamma_i \m_i^T \Sigma_i^{-1} \m_i K += -0.5 * VecVec(x, temp); // next line: a += \gamma_i \M_i^T \Sigma_i^{-1} \m_i a.AddMatVec(gamma, M_[i], kTrans, temp, 1.0); } } SpMatrix B(IvectorDim()); SubVector B_vec(B.Data(), IvectorDim()*(IvectorDim()+1)/2); B_vec.AddMatVec(1.0, U_, kTrans, Vector(utt_stats.gamma), 0.0); double ans = K + VecVec(mean, a) - 0.5 * VecSpVec(mean, B, mean); if (var != NULL) ans -= 0.5 * TraceSpSp(*var, B); return ans; } double IvectorExtractor::GetAcousticAuxfGconst( const IvectorExtractorUtteranceStats &utt_stats) const { return VecVec(Vector(utt_stats.gamma), gconsts_); } double IvectorExtractor::GetAcousticAuxfVariance( const IvectorExtractorUtteranceStats &utt_stats) const { if (utt_stats.S.empty()) { // we did not store the variance, so assume it's as predicted // by the model itself. // for each Gaussian i, we have a term -0.5 * gamma(i) * trace(Sigma[i] * Sigma[i]^{-1}) // = -0.5 * gamma(i) * FeatDim(). return -0.5 * utt_stats.gamma.Sum() * FeatDim(); } else { int32 I = NumGauss(); double ans = 0.0; for (int32 i = 0; i < I; i++) { double gamma = utt_stats.gamma(i); if (gamma != 0.0) { SpMatrix var(utt_stats.S[i]); var.Scale(1.0 / gamma); Vector mean(utt_stats.X.Row(i)); mean.Scale(1.0 / gamma); var.AddVec2(-1.0, mean); // get centered covariance.. ans += -0.5 * gamma * TraceSpSp(var, Sigma_inv_[i]); } } return ans; } } void IvectorExtractor::TransformIvectors(const MatrixBase &T, double new_ivector_offset) { Matrix Tinv(T); Tinv.Invert(); // w <-- w Tinv. (construct temporary copy with Matrix(w)) if (IvectorDependentWeights()) w_.AddMatMat(1.0, Matrix(w_), kNoTrans, Tinv, kNoTrans, 0.0); // next: M_i <-- M_i Tinv. (construct temporary copy with Matrix(M_[i])) for (int32 i = 0; i < NumGauss(); i++) M_[i].AddMatMat(1.0, Matrix(M_[i]), kNoTrans, Tinv, kNoTrans, 0.0); KALDI_LOG << "Setting iVector prior offset to " << new_ivector_offset; ivector_offset_ = new_ivector_offset; } void IvectorExtractor::Write(std::ostream &os, bool binary) const { WriteToken(os, binary, ""); WriteToken(os, binary, ""); w_.Write(os, binary); WriteToken(os, binary, ""); w_vec_.Write(os, binary); WriteToken(os, binary, ""); int32 size = M_.size(); WriteBasicType(os, binary, size); for (int32 i = 0; i < size; i++) M_[i].Write(os, binary); WriteToken(os, binary, ""); KALDI_ASSERT(size == static_cast(Sigma_inv_.size())); for (int32 i = 0; i < size; i++) Sigma_inv_[i].Write(os, binary); WriteToken(os, binary, ""); WriteBasicType(os, binary, ivector_offset_); WriteToken(os, binary, ""); } void IvectorExtractor::Read(std::istream &is, bool binary) { ExpectToken(is, binary, ""); ExpectToken(is, binary, ""); w_.Read(is, binary); ExpectToken(is, binary, ""); w_vec_.Read(is, binary); ExpectToken(is, binary, ""); int32 size; ReadBasicType(is, binary, &size); KALDI_ASSERT(size > 0); M_.resize(size); for (int32 i = 0; i < size; i++) M_[i].Read(is, binary); ExpectToken(is, binary, ""); Sigma_inv_.resize(size); for (int32 i = 0; i < size; i++) Sigma_inv_[i].Read(is, binary); ExpectToken(is, binary, ""); ReadBasicType(is, binary, &ivector_offset_); ExpectToken(is, binary, ""); ComputeDerivedVars(); } IvectorStats::IvectorStats(const IvectorExtractor &extractor, const IvectorStatsOptions &stats_opts): config_(stats_opts) { int32 S = extractor.IvectorDim(), D = extractor.FeatDim(), I = extractor.NumGauss(); KALDI_ASSERT(config_.num_samples_for_weights > 1); tot_auxf_ = 0.0; gamma_.Resize(I); Y_.resize(I); for (int32 i = 0; i < I; i++) Y_[i].Resize(D, S); R_.Resize(I, S * (S + 1) / 2); if (extractor.IvectorDependentWeights()) { Q_.Resize(I, S * (S + 1) / 2); G_.Resize(I, S); } if (stats_opts.update_variances) { S_.resize(I); for (int32 i = 0; i < I; i++) S_[i].Resize(D); } num_ivectors_ = 0; ivector_sum_.Resize(S); ivector_scatter_.Resize(S); } void IvectorStats::CommitStatsForM( const IvectorExtractor &extractor, const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &ivec_mean, const SpMatrix &ivec_var) { subspace_stats_lock_.Lock(); // We do the occupation stats here also. gamma_.AddVec(1.0, utt_stats.gamma); // Stats for the linear term in M: for (int32 i = 0; i < extractor.NumGauss(); i++) { Y_[i].AddVecVec(1.0, utt_stats.X.Row(i), Vector(ivec_mean)); } int32 ivector_dim = extractor.IvectorDim(); // Stats for the quadratic term in M: SpMatrix ivec_scatter(ivec_var); ivec_scatter.AddVec2(1.0, ivec_mean); SubVector ivec_scatter_vec(ivec_scatter.Data(), ivector_dim * (ivector_dim + 1) / 2); R_.AddVecVec(1.0, utt_stats.gamma, ivec_scatter_vec); subspace_stats_lock_.Unlock(); } void IvectorStats::CommitStatsForSigma( const IvectorExtractor &extractor, const IvectorExtractorUtteranceStats &utt_stats) { variance_stats_lock_.Lock(); // Storing the raw scatter statistics per Gaussian. In the update phase we'll // take into account some other terms relating to the model means and their // correlation with the data. for (int32 i = 0; i < extractor.NumGauss(); i++) S_[i].AddSp(1.0, utt_stats.S[i]); variance_stats_lock_.Unlock(); } // This function commits stats for a single sample of the ivector, // to update the weight projection w_. void IvectorStats::CommitStatsForWPoint( const IvectorExtractor &extractor, const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &ivector, double weight) { int32 num_gauss = extractor.NumGauss(); // Compare this function with GetIvectorDistWeight(), from which it // was derived. Vector logw_unnorm(num_gauss); logw_unnorm.AddMatVec(1.0, extractor.w_, kNoTrans, ivector, 0.0); Vector w(logw_unnorm); w.ApplySoftMax(); // now w is the weights. Vector linear_coeff(num_gauss); Vector quadratic_coeff(num_gauss); double gamma = utt_stats.gamma.Sum(); for (int32 i = 0; i < num_gauss; i++) { double gamma_i = utt_stats.gamma(i); double max_term = std::max(gamma_i, gamma * w(i)); linear_coeff(i) = gamma_i - gamma * w(i) + max_term * logw_unnorm(i); quadratic_coeff(i) = max_term; } weight_stats_lock_.Lock(); G_.AddVecVec(weight, linear_coeff, Vector(ivector)); int32 ivector_dim = extractor.IvectorDim(); SpMatrix outer_prod(ivector_dim); outer_prod.AddVec2(1.0, ivector); SubVector outer_prod_vec(outer_prod.Data(), ivector_dim * (ivector_dim + 1) / 2); Q_.AddVecVec(weight, quadratic_coeff, outer_prod_vec); weight_stats_lock_.Unlock(); } void IvectorStats::CommitStatsForW( const IvectorExtractor &extractor, const IvectorExtractorUtteranceStats &utt_stats, const VectorBase &ivec_mean, const SpMatrix &ivec_var) { KALDI_ASSERT(config_.num_samples_for_weights > 1); Matrix rand(config_.num_samples_for_weights, extractor.IvectorDim()); rand.SetRandn(); TpMatrix ivec_stddev(extractor.IvectorDim()); ivec_stddev.Cholesky(ivec_var); Matrix ivecs(config_.num_samples_for_weights, extractor.IvectorDim()); ivecs.AddMatTp(1.0, rand, kNoTrans, ivec_stddev, kTrans, 0.0); // Now make the ivecs zero-mean Vector avg_ivec(extractor.IvectorDim()); avg_ivec.AddRowSumMat(1.0 / config_.num_samples_for_weights, ivecs); ivecs.AddVecToRows(-1.0, avg_ivec); // Correct the variance for what we just did, so the expected // variance still has the correct value. ivecs.Scale(sqrt(config_.num_samples_for_weights / (config_.num_samples_for_weights - 1.0))); // Add the mean of the distribution to "ivecs". ivecs.AddVecToRows(1.0, ivec_mean); // "ivecs" is now a sample from the iVector distribution. for (int32 samp = 0; samp < config_.num_samples_for_weights; samp++) CommitStatsForWPoint(extractor, utt_stats, ivecs.Row(samp), 1.0 / config_.num_samples_for_weights); } void IvectorStats::CommitStatsForPrior(const VectorBase &ivec_mean, const SpMatrix &ivec_var) { SpMatrix ivec_scatter(ivec_var); ivec_scatter.AddVec2(1.0, ivec_mean); prior_stats_lock_.Lock(); num_ivectors_ += 1.0; ivector_sum_.AddVec(1.0, ivec_mean); ivector_scatter_.AddSp(1.0, ivec_scatter); prior_stats_lock_.Unlock(); } void IvectorStats::CommitStatsForUtterance( const IvectorExtractor &extractor, const IvectorExtractorUtteranceStats &utt_stats) { int32 ivector_dim = extractor.IvectorDim(); Vector ivec_mean(ivector_dim); SpMatrix ivec_var(ivector_dim); extractor.GetIvectorDistribution(utt_stats, &ivec_mean, &ivec_var); if (config_.compute_auxf) tot_auxf_ += extractor.GetAuxf(utt_stats, ivec_mean, &ivec_var); CommitStatsForM(extractor, utt_stats, ivec_mean, ivec_var); if (extractor.IvectorDependentWeights()) CommitStatsForW(extractor, utt_stats, ivec_mean, ivec_var); CommitStatsForPrior(ivec_mean, ivec_var); if (!S_.empty()) CommitStatsForSigma(extractor, utt_stats); } void IvectorStats::CheckDims(const IvectorExtractor &extractor) const { int32 S = extractor.IvectorDim(), D = extractor.FeatDim(), I = extractor.NumGauss(); KALDI_ASSERT(config_.num_samples_for_weights > 0); KALDI_ASSERT(gamma_.Dim() == I); KALDI_ASSERT(static_cast(Y_.size()) == I); for (int32 i = 0; i < I; i++) KALDI_ASSERT(Y_[i].NumRows() == D && Y_[i].NumCols() == S); KALDI_ASSERT(R_.NumRows() == I && R_.NumCols() == S*(S+1)/2); if (extractor.IvectorDependentWeights()) { KALDI_ASSERT(Q_.NumRows() == I && Q_.NumCols() == S*(S+1)/2); KALDI_ASSERT(G_.NumRows() == I && G_.NumCols() == S); } else { KALDI_ASSERT(Q_.NumRows() == 0); KALDI_ASSERT(G_.NumRows() == 0); } // S_ may be empty or not, depending on whether update_variances == true in // the options. if (!S_.empty()) { KALDI_ASSERT(static_cast(S_.size() == I)); for (int32 i = 0; i < I; i++) KALDI_ASSERT(S_[i].NumRows() == D); } KALDI_ASSERT(num_ivectors_ >= 0); KALDI_ASSERT(ivector_sum_.Dim() == S); KALDI_ASSERT(ivector_scatter_.NumRows() == S); } void IvectorStats::AccStatsForUtterance( const IvectorExtractor &extractor, const MatrixBase &feats, const Posterior &post) { typedef std::vector > VecType; CheckDims(extractor); int32 num_gauss = extractor.NumGauss(), feat_dim = extractor.FeatDim(); if (feat_dim != feats.NumCols()) { KALDI_ERR << "Feature dimension mismatch, expected " << feat_dim << ", got " << feats.NumCols(); } KALDI_ASSERT(static_cast(post.size()) == feats.NumRows()); bool update_variance = (!S_.empty()); // The zeroth and 1st-order stats are in "utt_stats". IvectorExtractorUtteranceStats utt_stats(num_gauss, feat_dim, update_variance); extractor.GetStats(feats, post, &utt_stats); CommitStatsForUtterance(extractor, utt_stats); } double IvectorStats::AccStatsForUtterance( const IvectorExtractor &extractor, const MatrixBase &feats, const FullGmm &fgmm) { int32 num_frames = feats.NumRows(); Posterior post(num_frames); double tot_log_like = 0.0; for (int32 t = 0; t < num_frames; t++) { SubVector frame(feats, t); Vector posterior(fgmm.NumGauss(), kUndefined); tot_log_like += fgmm.ComponentPosteriors(frame, &posterior); for (int32 i = 0; i < posterior.Dim(); i++) post[t].push_back(std::make_pair(i, posterior(i))); } AccStatsForUtterance(extractor, feats, post); return tot_log_like; } void IvectorStats::Add(const IvectorStats &other) { KALDI_ASSERT(config_.num_samples_for_weights == other.config_.num_samples_for_weights); double weight = 1.0; // will later make this configurable if needed. tot_auxf_ += weight * other.tot_auxf_; gamma_.AddVec(weight, other.gamma_); KALDI_ASSERT(Y_.size() == other.Y_.size()); for (size_t i = 0; i < Y_.size(); i++) Y_[i].AddMat(weight, other.Y_[i]); R_.AddMat(weight, other.R_); Q_.AddMat(weight, other.Q_); G_.AddMat(weight, other.G_); KALDI_ASSERT(S_.size() == other.S_.size()); for (size_t i = 0; i < S_.size(); i++) S_[i].AddSp(weight, other.S_[i]); num_ivectors_ += weight * other.num_ivectors_; ivector_sum_.AddVec(weight, other.ivector_sum_); ivector_scatter_.AddSp(weight, other.ivector_scatter_); } void IvectorStats::Write(std::ostream &os, bool binary) const { WriteToken(os, binary, ""); WriteToken(os, binary, ""); WriteBasicType(os, binary, tot_auxf_); WriteToken(os, binary, ""); gamma_.Write(os, binary); WriteToken(os, binary, ""); int32 size = Y_.size(); WriteBasicType(os, binary, size); for (int32 i = 0; i < size; i++) Y_[i].Write(os, binary); WriteToken(os, binary, ""); R_.Write(os, binary); WriteToken(os, binary, ""); Q_.Write(os, binary); WriteToken(os, binary, ""); G_.Write(os, binary); WriteToken(os, binary, ""); size = S_.size(); WriteBasicType(os, binary, size); for (int32 i = 0; i < size; i++) S_[i].Write(os, binary); WriteToken(os, binary, ""); WriteBasicType(os, binary, num_ivectors_); WriteToken(os, binary, ""); ivector_sum_.Write(os, binary); WriteToken(os, binary, ""); ivector_scatter_.Write(os, binary); WriteToken(os, binary, ""); } void IvectorStats::Read(std::istream &is, bool binary, bool add) { ExpectToken(is, binary, ""); ExpectToken(is, binary, ""); ReadBasicType(is, binary, &tot_auxf_, add); ExpectToken(is, binary, ""); gamma_.Read(is, binary, add); ExpectToken(is, binary, ""); int32 size; ReadBasicType(is, binary, &size); Y_.resize(size); for (int32 i = 0; i < size; i++) Y_[i].Read(is, binary, add); ExpectToken(is, binary, ""); R_.Read(is, binary, add); ExpectToken(is, binary, ""); Q_.Read(is, binary, add); ExpectToken(is, binary, ""); G_.Read(is, binary, add); ExpectToken(is, binary, ""); ReadBasicType(is, binary, &size); S_.resize(size); for (int32 i = 0; i < size; i++) S_[i].Read(is, binary, add); ExpectToken(is, binary, ""); ReadBasicType(is, binary, &num_ivectors_, add); ExpectToken(is, binary, ""); ivector_sum_.Read(is, binary, add); ExpectToken(is, binary, ""); ivector_scatter_.Read(is, binary, add); ExpectToken(is, binary, ""); } double IvectorStats::Update(const IvectorExtractorEstimationOptions &opts, IvectorExtractor *extractor) const { CheckDims(*extractor); if (tot_auxf_ != 0.0) { KALDI_LOG << "Overall auxf/frame on training data was " << (tot_auxf_/gamma_.Sum()) << " per frame over " << gamma_.Sum() << " frames."; } double ans = 0.0; ans += UpdateProjections(opts, extractor); if (extractor->IvectorDependentWeights()) ans += UpdateWeights(opts, extractor); if (!S_.empty()) ans += UpdateVariances(opts, extractor); ans += UpdatePrior(opts, extractor); // This will also transform the ivector // space. Note: this must be done as the // last stage, because it will make the // stats invalid for that model. KALDI_LOG << "Overall objective-function improvement per frame was " << ans; extractor->ComputeDerivedVars(); return ans; } double IvectorStats::UpdateProjection( const IvectorExtractorEstimationOptions &opts, int32 i, IvectorExtractor *extractor) const { int32 I = extractor->NumGauss(), S = extractor->IvectorDim(); KALDI_ASSERT(i >= 0 && i < I); /* For Gaussian index i, maximize the auxiliary function Q_i(x) = tr(M_i^T Sigma_i^{-1} Y_i) - 0.5 tr(Sigma_i^{-1} M_i R_i M_i^T) */ if (gamma_(i) < opts.gaussian_min_count) { KALDI_WARN << "Skipping Gaussian index " << i << " because count " << gamma_(i) << " is below min-count."; return 0.0; } SpMatrix R(S, kUndefined), SigmaInv(extractor->Sigma_inv_[i]); SubVector R_vec(R_, i); // i'th row of R; vectorized form of SpMatrix. SubVector R_sp(R.Data(), S * (S+1) / 2); R_sp.CopyFromVec(R_vec); // copy to SpMatrix's memory. Matrix M(extractor->M_[i]); SolverOptions solver_opts; solver_opts.name = "M"; solver_opts.diagonal_precondition = true; double impr = SolveQuadraticMatrixProblem(R, Y_[i], SigmaInv, solver_opts, &M), gamma = gamma_(i); if (i < 4) { KALDI_VLOG(1) << "Objf impr for M for Gaussian index " << i << " is " << (impr / gamma) << " per frame over " << gamma << " frames."; } extractor->M_[i].CopyFromMat(M); return impr; } class IvectorExtractorUpdateProjectionClass { public: IvectorExtractorUpdateProjectionClass(const IvectorStats &stats, const IvectorExtractorEstimationOptions &opts, int32 i, IvectorExtractor *extractor, double *tot_impr): stats_(stats), opts_(opts), i_(i), extractor_(extractor), tot_impr_(tot_impr), impr_(0.0) { } void operator () () { impr_ = stats_.UpdateProjection(opts_, i_, extractor_); } ~IvectorExtractorUpdateProjectionClass() { *tot_impr_ += impr_; } private: const IvectorStats &stats_; const IvectorExtractorEstimationOptions &opts_; int32 i_; IvectorExtractor *extractor_; double *tot_impr_; double impr_; }; double IvectorStats::UpdateProjections( const IvectorExtractorEstimationOptions &opts, IvectorExtractor *extractor) const { int32 I = extractor->NumGauss(); double tot_impr = 0.0; { TaskSequencerConfig sequencer_opts; sequencer_opts.num_threads = opts.num_threads; TaskSequencer sequencer( sequencer_opts); for (int32 i = 0; i < I; i++) sequencer.Run(new IvectorExtractorUpdateProjectionClass( *this, opts, i, extractor, &tot_impr)); } double count = gamma_.Sum(); KALDI_LOG << "Overall objective function improvement for M (mean projections) " << "was " << (tot_impr / count) << " per frame over " << count << " frames."; return tot_impr / count; } double IvectorStats::UpdateVariances( const IvectorExtractorEstimationOptions &opts, IvectorExtractor *extractor) const { int32 num_gauss = extractor->NumGauss(), feat_dim = extractor->FeatDim(), ivector_dim = extractor->IvectorDim(); KALDI_ASSERT(!S_.empty()); double tot_objf_impr = 0.0; // "raw_variances" will be the variances directly from // the stats, without any flooring. std::vector > raw_variances(num_gauss); SpMatrix var_floor(feat_dim); double var_floor_count = 0.0; for (int32 i = 0; i < num_gauss; i++) { if (gamma_(i) < opts.gaussian_min_count) continue; // warned in UpdateProjections SpMatrix &S(raw_variances[i]); S = S_[i]; // Set it to the raw scatter statistics. // The equations for estimating the variance are similar to // those used in SGMMs. We need to convert it to a centered // covariance, and for this we can use a combination of other // stats and the model parameters. Matrix M(extractor->M_[i]); // Y * M^T. Matrix YM(feat_dim, feat_dim); YM.AddMatMat(1.0, Y_[i], kNoTrans, M, kTrans, 0.0); Matrix YMMY(YM, kTrans); YMMY.AddMat(1.0, YM); // Now, YMMY = Y * M^T + M * Y^T. This is a kind of cross-term // between the mean and the data, which we subtract. SpMatrix YMMY_sp(YMMY, kTakeMeanAndCheck); S.AddSp(-1.0, YMMY_sp); // Add in a mean-squared term. SpMatrix R(ivector_dim); // will be scatter of iVectors, weighted // by count for this Gaussian. SubVector R_vec(R.Data(), ivector_dim * (ivector_dim + 1) / 2); R_vec.CopyFromVec(R_.Row(i)); // S.AddMat2Sp(1.0, M, kNoTrans, R, 1.0); var_floor.AddSp(1.0, S); var_floor_count += gamma_(i); S.Scale(1.0 / gamma_(i)); } KALDI_ASSERT(var_floor_count > 0.0); KALDI_ASSERT(opts.variance_floor_factor > 0.0 && opts.variance_floor_factor <= 1.0); var_floor.Scale(opts.variance_floor_factor / var_floor_count); int32 tot_num_floored = 0; for (int32 i = 0; i < num_gauss; i++) { SpMatrix &S(raw_variances[i]); // un-floored variance. if (S.NumRows() == 0) continue; // due to low count. SpMatrix floored_var(S); SpMatrix old_inv_var(extractor->Sigma_inv_[i]); int32 num_floored = floored_var.ApplyFloor(var_floor); tot_num_floored += num_floored; if (num_floored > 0) KALDI_LOG << "For Gaussian index " << i << ", floored " << num_floored << " eigenvalues of variance."; // this objf is per frame; double old_objf = -0.5 * (TraceSpSp(S, old_inv_var) - old_inv_var.LogPosDefDet()); SpMatrix new_inv_var(floored_var); new_inv_var.Invert(); double new_objf = -0.5 * (TraceSpSp(S, new_inv_var) - new_inv_var.LogPosDefDet()); if (i < 4) { KALDI_VLOG(1) << "Objf impr/frame for variance for Gaussian index " << i << " was " << (new_objf - old_objf); } tot_objf_impr += gamma_(i) * (new_objf - old_objf); extractor->Sigma_inv_[i].CopyFromSp(new_inv_var); } double floored_percent = tot_num_floored * 100.0 / (num_gauss * feat_dim); KALDI_LOG << "Floored " << floored_percent << "% of all Gaussian eigenvalues"; KALDI_LOG << "Overall objf impr/frame for variances was " << (tot_objf_impr / gamma_.Sum()) << " over " << gamma_.Sum() << " frames."; return tot_objf_impr / gamma_.Sum(); } double IvectorStats::UpdateWeight( const IvectorExtractorEstimationOptions &opts, int32 i, IvectorExtractor *extractor) const { int32 num_gauss = extractor->NumGauss(), ivector_dim = extractor->IvectorDim(); KALDI_ASSERT(i >= 0 && i < num_gauss); SolverOptions solver_opts; solver_opts.diagonal_precondition = true; solver_opts.name = "w"; SubVector w_i(extractor->w_, i); SubVector g_i(G_, i); SpMatrix Q(ivector_dim); SubVector Q_vec(Q.Data(), ivector_dim * (ivector_dim + 1) / 2); Q_vec.CopyFromVec(Q_.Row(i)); double objf_impr = SolveQuadraticProblem(Q, g_i, solver_opts, &w_i); if (i < 4 && gamma_(i) != 0.0) { KALDI_VLOG(1) << "Auxf impr/frame for Gaussian index " << i << " for weights is " << (objf_impr / gamma_(i)) << " over " << gamma_(i) << " frames."; } return objf_impr; } class IvectorExtractorUpdateWeightClass { public: IvectorExtractorUpdateWeightClass(const IvectorStats &stats, const IvectorExtractorEstimationOptions &opts, int32 i, IvectorExtractor *extractor, double *tot_impr): stats_(stats), opts_(opts), i_(i), extractor_(extractor), tot_impr_(tot_impr), impr_(0.0) { } void operator () () { impr_ = stats_.UpdateWeight(opts_, i_, extractor_); } ~IvectorExtractorUpdateWeightClass() { *tot_impr_ += impr_; } private: const IvectorStats &stats_; const IvectorExtractorEstimationOptions &opts_; int32 i_; IvectorExtractor *extractor_; double *tot_impr_; double impr_; }; double IvectorStats::UpdateWeights( const IvectorExtractorEstimationOptions &opts, IvectorExtractor *extractor) const { int32 I = extractor->NumGauss(); double tot_impr = 0.0; { TaskSequencerConfig sequencer_opts; sequencer_opts.num_threads = opts.num_threads; TaskSequencer sequencer( sequencer_opts); for (int32 i = 0; i < I; i++) sequencer.Run(new IvectorExtractorUpdateWeightClass( *this, opts, i, extractor, &tot_impr)); } double num_frames = gamma_.Sum(); KALDI_LOG << "Overall auxf impr/frame from weight update is " << (tot_impr / num_frames) << " over " << num_frames << " frames."; return tot_impr / num_frames; } double IvectorStats::PriorDiagnostics(double old_ivector_offset) const { // The iVectors had a centered covariance "covar"; we want to figure out // the objective-function change from rescaling. It's as if we were // formerly modeling "covar" with the unit matrix, and we're now modeling // it with "covar" itself. This is ignoring flooring issues. Of course, // we implement it through rescaling the space, but it has the same effect. // We also need to take into account that before the rescaling etc., the // old mean might have been wrong. int32 ivector_dim = ivector_sum_.Dim(); Vector sum(ivector_sum_); sum.Scale(1.0 / num_ivectors_); SpMatrix covar(ivector_scatter_); covar.Scale(1.0 / num_ivectors_); covar.AddVec2(-1.0, sum); // Get the centered covariance. // Now work out the offset from the old prior's mean. Vector mean_offset(sum); mean_offset(0) -= old_ivector_offset; SpMatrix old_covar(covar); // the covariance around the old mean. old_covar.AddVec2(1.0, mean_offset); // old likelihood = -0.5 * (Trace(I old_covar) + logdet(I) + [ignored]) double old_like = -0.5 * old_covar.Trace(); // new likelihood is if we updated the variance to equal "covar"... this isn't // how we did it (we use rescaling of the ivectors) but it has the same // effect. -0.5 * (Trace(covar^{-1} covar) + logdet(covar)) double new_like = -0.5 * (ivector_dim + covar.LogPosDefDet()), like_change = new_like - old_like, like_change_per_frame = like_change * num_ivectors_ / gamma_.Sum(); KALDI_LOG << "Overall auxf improvement from prior is " << like_change_per_frame << ", or " << like_change << " per iVector."; return like_change_per_frame; // we'll be adding this to other per-frame // quantities. } double IvectorStats::UpdatePrior( const IvectorExtractorEstimationOptions &opts, IvectorExtractor *extractor) const { KALDI_ASSERT(num_ivectors_ > 0.0); Vector sum(ivector_sum_); sum.Scale(1.0 / num_ivectors_); SpMatrix covar(ivector_scatter_); covar.Scale(1.0 / num_ivectors_); covar.AddVec2(-1.0, sum); // Get the centered covariance. int32 ivector_dim = extractor->IvectorDim(); Vector s(ivector_dim); Matrix P(ivector_dim, ivector_dim); // decompose covar = P diag(s) P^T: covar.Eig(&s, &P); KALDI_LOG << "Eigenvalues of iVector covariance range from " << s.Min() << " to " << s.Max(); int32 num_floored = s.ApplyFloor(1.0e-07); if (num_floored > 0) KALDI_WARN << "Floored " << num_floored << " eigenvalues of covar " << "of iVectors."; Matrix T(P, kTrans); { // set T to a transformation that makes covar unit // (modulo floored eigenvalues). Vector scales(s); scales.ApplyPow(-0.5); T.MulRowsVec(scales); if (num_floored == 0) { // a check.. SpMatrix Tproj(ivector_dim); Tproj.AddMat2Sp(1.0, T, kNoTrans, covar, 0.0); KALDI_ASSERT(Tproj.IsUnit(1.0e-06)); } } Vector sum_proj(ivector_dim); sum_proj.AddMatVec(1.0, T, kNoTrans, sum, 0.0); KALDI_ASSERT(sum_proj.Norm(2.0) != 0.0); // We need a projection that (like T) makes "covar" unit, // but also that sends "sum" to a multiple of the vector e0 = [ 1 0 0 0 .. ]. // We'll do this by a transform that follows T, of the form // (I - 2 a a^T), where a is unit. [i.e. a Householder reflection]. // Firstly, let x equal sum_proj normalized to unit length. // We'll let a = alpha x + beta e0, for suitable coefficients alpha and beta, // To project sum_proj (or equivalenty, x) to a multiple of e0, we'll need that // the x term in // (I - 2(alpha x + beta e0)(alpha x + beta e0) x // equals zero., i.e. 1 - 2 alpha (alpha x^T x + beta e0^T x) == 0, // (1 - 2 alpha^2 - 2 alpha beta x0) = 0 // To ensure that a is unit, we require that // (alpha x + beta e0).(alpha x + beta e0) = 1, i.e. // alpha^2 + beta^2 + 2 alpha beta x0 = 1 // at wolframalpha.com, // Solve[ {a^2 + b^2 + 2 a b x = 1}, {1 - 2 a^2 - 2 a b x = 0}, {a, b} ] // gives different solutions, but the one that keeps the offset positive // after projection seems to be: // alpha = 1/(sqrt(2)sqrt(1 - x0)), beta = -alpha Matrix U(ivector_dim, ivector_dim); U.SetUnit(); Vector x(sum_proj); x.Scale(1.0 / x.Norm(2.0)); double x0 = x(0), alpha, beta; alpha = 1.0 / (M_SQRT2 * sqrt(1.0 - x0)); beta = -alpha; Vector a(x); a.Scale(alpha); a(0) += beta; U.AddVecVec(-2.0, a, a); Matrix V(ivector_dim, ivector_dim); V.AddMatMat(1.0, U, kNoTrans, T, kNoTrans, 0.0); if (num_floored == 0) { // a check.. SpMatrix Vproj(ivector_dim); Vproj.AddMat2Sp(1.0, V, kNoTrans, covar, 0.0); KALDI_ASSERT(Vproj.IsUnit(1.0e-04)); } Vector sum_vproj(ivector_dim); sum_vproj.AddMatVec(1.0, V, kNoTrans, sum, 0.0); // Make sure sum_vproj is of the form [ x 0 0 0 .. ] with x > 0. // (the x > 0 part isn't really necessary, it's just nice to know.) KALDI_ASSERT(ApproxEqual(sum_vproj(0), sum_vproj.Norm(2.0))); double ans = PriorDiagnostics(extractor->ivector_offset_); extractor->TransformIvectors(V, sum_vproj(0)); return ans; } } // namespace kaldi