// SPDX-License-Identifier: Apache-2.0 // // Copyright 2008-2016 Conrad Sanderson (http://conradsanderson.id.au) // Copyright 2008-2016 National ICT Australia (NICTA) // // 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 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ------------------------------------------------------------------------ //! \addtogroup gmm_full //! @{ namespace gmm_priv { template inline gmm_full::~gmm_full() { arma_extra_debug_sigprint_this(this); arma_type_check(( (is_same_type::value == false) && (is_same_type::value == false) )); } template inline gmm_full::gmm_full() { arma_extra_debug_sigprint_this(this); } template inline gmm_full::gmm_full(const gmm_full& x) { arma_extra_debug_sigprint_this(this); init(x); } template inline gmm_full& gmm_full::operator=(const gmm_full& x) { arma_extra_debug_sigprint(); init(x); return *this; } template inline gmm_full::gmm_full(const gmm_diag& x) { arma_extra_debug_sigprint_this(this); init(x); } template inline gmm_full& gmm_full::operator=(const gmm_diag& x) { arma_extra_debug_sigprint(); init(x); return *this; } template inline gmm_full::gmm_full(const uword in_n_dims, const uword in_n_gaus) { arma_extra_debug_sigprint_this(this); init(in_n_dims, in_n_gaus); } template inline void gmm_full::reset() { arma_extra_debug_sigprint(); init(0, 0); } template inline void gmm_full::reset(const uword in_n_dims, const uword in_n_gaus) { arma_extra_debug_sigprint(); init(in_n_dims, in_n_gaus); } template template inline void gmm_full::set_params(const Base& in_means_expr, const BaseCube& in_fcovs_expr, const Base& in_hefts_expr) { arma_extra_debug_sigprint(); const unwrap tmp1(in_means_expr.get_ref()); const unwrap_cube tmp2(in_fcovs_expr.get_ref()); const unwrap tmp3(in_hefts_expr.get_ref()); const Mat & in_means = tmp1.M; const Cube& in_fcovs = tmp2.M; const Mat & in_hefts = tmp3.M; arma_debug_check ( (in_means.n_cols != in_fcovs.n_slices) || (in_means.n_rows != in_fcovs.n_rows) || (in_fcovs.n_rows != in_fcovs.n_cols) || (in_hefts.n_cols != in_means.n_cols) || (in_hefts.n_rows != 1), "gmm_full::set_params(): given parameters have inconsistent and/or wrong sizes" ); arma_debug_check( (in_means.internal_has_nonfinite()), "gmm_full::set_params(): given means have non-finite values" ); arma_debug_check( (in_fcovs.internal_has_nonfinite()), "gmm_full::set_params(): given fcovs have non-finite values" ); arma_debug_check( (in_hefts.internal_has_nonfinite()), "gmm_full::set_params(): given hefts have non-finite values" ); for(uword g=0; g < in_fcovs.n_slices; ++g) { arma_debug_check( (any(diagvec(in_fcovs.slice(g)) <= eT(0))), "gmm_full::set_params(): given fcovs have negative or zero values on diagonals" ); } arma_debug_check( (any(vectorise(in_hefts) < eT(0))), "gmm_full::set_params(): given hefts have negative values" ); const eT s = accu(in_hefts); arma_debug_check( ((s < (eT(1) - eT(0.001))) || (s > (eT(1) + eT(0.001)))), "gmm_full::set_params(): sum of given hefts is not 1" ); access::rw(means) = in_means; access::rw(fcovs) = in_fcovs; access::rw(hefts) = in_hefts; init_constants(); } template template inline void gmm_full::set_means(const Base& in_means_expr) { arma_extra_debug_sigprint(); const unwrap tmp(in_means_expr.get_ref()); const Mat& in_means = tmp.M; arma_debug_check( (arma::size(in_means) != arma::size(means)), "gmm_full::set_means(): given means have incompatible size" ); arma_debug_check( (in_means.internal_has_nonfinite()), "gmm_full::set_means(): given means have non-finite values" ); access::rw(means) = in_means; } template template inline void gmm_full::set_fcovs(const BaseCube& in_fcovs_expr) { arma_extra_debug_sigprint(); const unwrap_cube tmp(in_fcovs_expr.get_ref()); const Cube& in_fcovs = tmp.M; arma_debug_check( (arma::size(in_fcovs) != arma::size(fcovs)), "gmm_full::set_fcovs(): given fcovs have incompatible size" ); arma_debug_check( (in_fcovs.internal_has_nonfinite()), "gmm_full::set_fcovs(): given fcovs have non-finite values" ); for(uword i=0; i < in_fcovs.n_slices; ++i) { arma_debug_check( (any(diagvec(in_fcovs.slice(i)) <= eT(0))), "gmm_full::set_fcovs(): given fcovs have negative or zero values on diagonals" ); } access::rw(fcovs) = in_fcovs; init_constants(); } template template inline void gmm_full::set_hefts(const Base& in_hefts_expr) { arma_extra_debug_sigprint(); const unwrap tmp(in_hefts_expr.get_ref()); const Mat& in_hefts = tmp.M; arma_debug_check( (arma::size(in_hefts) != arma::size(hefts)), "gmm_full::set_hefts(): given hefts have incompatible size" ); arma_debug_check( (in_hefts.internal_has_nonfinite()), "gmm_full::set_hefts(): given hefts have non-finite values" ); arma_debug_check( (any(vectorise(in_hefts) < eT(0))), "gmm_full::set_hefts(): given hefts have negative values" ); const eT s = accu(in_hefts); arma_debug_check( ((s < (eT(1) - eT(0.001))) || (s > (eT(1) + eT(0.001)))), "gmm_full::set_hefts(): sum of given hefts is not 1" ); // make sure all hefts are positive and non-zero const eT* in_hefts_mem = in_hefts.memptr(); eT* hefts_mem = access::rw(hefts).memptr(); for(uword i=0; i < hefts.n_elem; ++i) { hefts_mem[i] = (std::max)( in_hefts_mem[i], std::numeric_limits::min() ); } access::rw(hefts) /= accu(hefts); log_hefts = log(hefts); } template inline uword gmm_full::n_dims() const { return means.n_rows; } template inline uword gmm_full::n_gaus() const { return means.n_cols; } template inline bool gmm_full::load(const std::string name) { arma_extra_debug_sigprint(); field< Mat > storage; bool status = storage.load(name, arma_binary); if( (status == false) || (storage.n_elem < 2) ) { reset(); arma_debug_warn_level(3, "gmm_full::load(): problem with loading or incompatible format"); return false; } uword count = 0; const Mat& storage_means = storage(count); ++count; const Mat& storage_hefts = storage(count); ++count; const uword N_dims = storage_means.n_rows; const uword N_gaus = storage_means.n_cols; if( (storage.n_elem != (N_gaus + 2)) || (storage_hefts.n_rows != 1) || (storage_hefts.n_cols != N_gaus) ) { reset(); arma_debug_warn_level(3, "gmm_full::load(): incompatible format"); return false; } reset(N_dims, N_gaus); access::rw(means) = storage_means; access::rw(hefts) = storage_hefts; for(uword g=0; g < N_gaus; ++g) { const Mat& storage_fcov = storage(count); ++count; if( (storage_fcov.n_rows != N_dims) || (storage_fcov.n_cols != N_dims) ) { reset(); arma_debug_warn_level(3, "gmm_full::load(): incompatible format"); return false; } access::rw(fcovs).slice(g) = storage_fcov; } init_constants(); return true; } template inline bool gmm_full::save(const std::string name) const { arma_extra_debug_sigprint(); const uword N_gaus = means.n_cols; field< Mat > storage(2 + N_gaus); uword count = 0; storage(count) = means; ++count; storage(count) = hefts; ++count; for(uword g=0; g < N_gaus; ++g) { storage(count) = fcovs.slice(g); ++count; } const bool status = storage.save(name, arma_binary); return status; } template inline Col gmm_full::generate() const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; Col out( (N_gaus > 0) ? N_dims : uword(0), arma_nozeros_indicator() ); Col tmp( (N_gaus > 0) ? N_dims : uword(0), fill::randn ); if(N_gaus > 0) { const double val = randu(); double csum = double(0); uword gaus_id = 0; for(uword j=0; j < N_gaus; ++j) { csum += hefts[j]; if(val <= csum) { gaus_id = j; break; } } out = chol_fcovs.slice(gaus_id) * tmp; out += means.col(gaus_id); } return out; } template inline Mat gmm_full::generate(const uword N_vec) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; Mat out( ( (N_gaus > 0) ? N_dims : uword(0) ), N_vec, arma_nozeros_indicator() ); Mat tmp( ( (N_gaus > 0) ? N_dims : uword(0) ), N_vec, fill::randn ); if(N_gaus > 0) { const eT* hefts_mem = hefts.memptr(); for(uword i=0; i < N_vec; ++i) { const double val = randu(); double csum = double(0); uword gaus_id = 0; for(uword j=0; j < N_gaus; ++j) { csum += hefts_mem[j]; if(val <= csum) { gaus_id = j; break; } } Col out_vec(out.colptr(i), N_dims, false, true); Col tmp_vec(tmp.colptr(i), N_dims, false, true); out_vec = chol_fcovs.slice(gaus_id) * tmp_vec; out_vec += means.col(gaus_id); } } return out; } template template inline eT gmm_full::log_p(const T1& expr, const gmm_empty_arg& junk1, typename enable_if<((is_arma_type::value) && (resolves_to_colvector::value == true))>::result* junk2) const { arma_extra_debug_sigprint(); arma_ignore(junk1); arma_ignore(junk2); const uword N_dims = means.n_rows; const quasi_unwrap U(expr); arma_debug_check( (U.M.n_rows != N_dims), "gmm_full::log_p(): incompatible dimensions" ); return internal_scalar_log_p( U.M.memptr() ); } template template inline eT gmm_full::log_p(const T1& expr, const uword gaus_id, typename enable_if<((is_arma_type::value) && (resolves_to_colvector::value == true))>::result* junk2) const { arma_extra_debug_sigprint(); arma_ignore(junk2); const uword N_dims = means.n_rows; const quasi_unwrap U(expr); arma_debug_check( (U.M.n_rows != N_dims), "gmm_full::log_p(): incompatible dimensions" ); arma_debug_check( (gaus_id >= means.n_cols), "gmm_full::log_p(): specified gaussian is out of range" ); return internal_scalar_log_p( U.M.memptr(), gaus_id ); } template template inline Row gmm_full::log_p(const T1& expr, const gmm_empty_arg& junk1, typename enable_if<((is_arma_type::value) && (resolves_to_colvector::value == false))>::result* junk2) const { arma_extra_debug_sigprint(); arma_ignore(junk1); arma_ignore(junk2); const quasi_unwrap tmp(expr); const Mat& X = tmp.M; return internal_vec_log_p(X); } template template inline Row gmm_full::log_p(const T1& expr, const uword gaus_id, typename enable_if<((is_arma_type::value) && (resolves_to_colvector::value == false))>::result* junk2) const { arma_extra_debug_sigprint(); arma_ignore(junk2); const quasi_unwrap tmp(expr); const Mat& X = tmp.M; return internal_vec_log_p(X, gaus_id); } template template inline eT gmm_full::sum_log_p(const Base& expr) const { arma_extra_debug_sigprint(); const quasi_unwrap tmp(expr.get_ref()); const Mat& X = tmp.M; return internal_sum_log_p(X); } template template inline eT gmm_full::sum_log_p(const Base& expr, const uword gaus_id) const { arma_extra_debug_sigprint(); const quasi_unwrap tmp(expr.get_ref()); const Mat& X = tmp.M; return internal_sum_log_p(X, gaus_id); } template template inline eT gmm_full::avg_log_p(const Base& expr) const { arma_extra_debug_sigprint(); const quasi_unwrap tmp(expr.get_ref()); const Mat& X = tmp.M; return internal_avg_log_p(X); } template template inline eT gmm_full::avg_log_p(const Base& expr, const uword gaus_id) const { arma_extra_debug_sigprint(); const quasi_unwrap tmp(expr.get_ref()); const Mat& X = tmp.M; return internal_avg_log_p(X, gaus_id); } template template inline uword gmm_full::assign(const T1& expr, const gmm_dist_mode& dist, typename enable_if<((is_arma_type::value) && (resolves_to_colvector::value == true))>::result* junk) const { arma_extra_debug_sigprint(); arma_ignore(junk); const quasi_unwrap tmp(expr); const Mat& X = tmp.M; return internal_scalar_assign(X, dist); } template template inline urowvec gmm_full::assign(const T1& expr, const gmm_dist_mode& dist, typename enable_if<((is_arma_type::value) && (resolves_to_colvector::value == false))>::result* junk) const { arma_extra_debug_sigprint(); arma_ignore(junk); urowvec out; const quasi_unwrap tmp(expr); const Mat& X = tmp.M; internal_vec_assign(out, X, dist); return out; } template template inline urowvec gmm_full::raw_hist(const Base& expr, const gmm_dist_mode& dist_mode) const { arma_extra_debug_sigprint(); const unwrap tmp(expr.get_ref()); const Mat& X = tmp.M; arma_debug_check( (X.n_rows != means.n_rows), "gmm_full::raw_hist(): incompatible dimensions" ); arma_debug_check( ((dist_mode != eucl_dist) && (dist_mode != prob_dist)), "gmm_full::raw_hist(): unsupported distance mode" ); urowvec hist; internal_raw_hist(hist, X, dist_mode); return hist; } template template inline Row gmm_full::norm_hist(const Base& expr, const gmm_dist_mode& dist_mode) const { arma_extra_debug_sigprint(); const unwrap tmp(expr.get_ref()); const Mat& X = tmp.M; arma_debug_check( (X.n_rows != means.n_rows), "gmm_full::norm_hist(): incompatible dimensions" ); arma_debug_check( ((dist_mode != eucl_dist) && (dist_mode != prob_dist)), "gmm_full::norm_hist(): unsupported distance mode" ); urowvec hist; internal_raw_hist(hist, X, dist_mode); const uword hist_n_elem = hist.n_elem; const uword* hist_mem = hist.memptr(); eT acc = eT(0); for(uword i=0; i out(hist_n_elem, arma_nozeros_indicator()); eT* out_mem = out.memptr(); for(uword i=0; i template inline bool gmm_full::learn ( const Base& data, const uword N_gaus, const gmm_dist_mode& dist_mode, const gmm_seed_mode& seed_mode, const uword km_iter, const uword em_iter, const eT var_floor, const bool print_mode ) { arma_extra_debug_sigprint(); const bool dist_mode_ok = (dist_mode == eucl_dist) || (dist_mode == maha_dist); const bool seed_mode_ok = \ (seed_mode == keep_existing) || (seed_mode == static_subset) || (seed_mode == static_spread) || (seed_mode == random_subset) || (seed_mode == random_spread); arma_debug_check( (dist_mode_ok == false), "gmm_full::learn(): dist_mode must be eucl_dist or maha_dist" ); arma_debug_check( (seed_mode_ok == false), "gmm_full::learn(): unknown seed_mode" ); arma_debug_check( (var_floor < eT(0) ), "gmm_full::learn(): variance floor is negative" ); const unwrap tmp_X(data.get_ref()); const Mat& X = tmp_X.M; if(X.is_empty() ) { arma_debug_warn_level(3, "gmm_full::learn(): given matrix is empty" ); return false; } if(X.internal_has_nonfinite()) { arma_debug_warn_level(3, "gmm_full::learn(): given matrix has non-finite values"); return false; } if(N_gaus == 0) { reset(); return true; } if(dist_mode == maha_dist) { mah_aux = var(X,1,1); const uword mah_aux_n_elem = mah_aux.n_elem; eT* mah_aux_mem = mah_aux.memptr(); for(uword i=0; i < mah_aux_n_elem; ++i) { const eT val = mah_aux_mem[i]; mah_aux_mem[i] = ((val != eT(0)) && arma_isfinite(val)) ? eT(1) / val : eT(1); } } // copy current model, in case of failure by k-means and/or EM const gmm_full orig = (*this); // initial means if(seed_mode == keep_existing) { if(means.is_empty() ) { arma_debug_warn_level(3, "gmm_full::learn(): no existing means" ); return false; } if(X.n_rows != means.n_rows) { arma_debug_warn_level(3, "gmm_full::learn(): dimensionality mismatch"); return false; } // TODO: also check for number of vectors? } else { if(X.n_cols < N_gaus) { arma_debug_warn_level(3, "gmm_full::learn(): number of vectors is less than number of gaussians"); return false; } reset(X.n_rows, N_gaus); if(print_mode) { get_cout_stream() << "gmm_full::learn(): generating initial means\n"; get_cout_stream().flush(); } if(dist_mode == eucl_dist) { generate_initial_means<1>(X, seed_mode); } else if(dist_mode == maha_dist) { generate_initial_means<2>(X, seed_mode); } } // k-means if(km_iter > 0) { const arma_ostream_state stream_state(get_cout_stream()); bool status = false; if(dist_mode == eucl_dist) { status = km_iterate<1>(X, km_iter, print_mode); } else if(dist_mode == maha_dist) { status = km_iterate<2>(X, km_iter, print_mode); } stream_state.restore(get_cout_stream()); if(status == false) { arma_debug_warn_level(3, "gmm_full::learn(): k-means algorithm failed; not enough data, or too many gaussians requested"); init(orig); return false; } } // initial fcovs const eT var_floor_actual = (eT(var_floor) > eT(0)) ? eT(var_floor) : std::numeric_limits::min(); if(seed_mode != keep_existing) { if(print_mode) { get_cout_stream() << "gmm_full::learn(): generating initial covariances\n"; get_cout_stream().flush(); } if(dist_mode == eucl_dist) { generate_initial_params<1>(X, var_floor_actual); } else if(dist_mode == maha_dist) { generate_initial_params<2>(X, var_floor_actual); } } // EM algorithm if(em_iter > 0) { const arma_ostream_state stream_state(get_cout_stream()); const bool status = em_iterate(X, em_iter, var_floor_actual, print_mode); stream_state.restore(get_cout_stream()); if(status == false) { arma_debug_warn_level(3, "gmm_full::learn(): EM algorithm failed"); init(orig); return false; } } mah_aux.reset(); init_constants(); return true; } // // // template inline void gmm_full::init(const gmm_full& x) { arma_extra_debug_sigprint(); gmm_full& t = *this; if(&t != &x) { access::rw(t.means) = x.means; access::rw(t.fcovs) = x.fcovs; access::rw(t.hefts) = x.hefts; init_constants(); } } template inline void gmm_full::init(const gmm_diag& x) { arma_extra_debug_sigprint(); access::rw(hefts) = x.hefts; access::rw(means) = x.means; const uword N_dims = x.means.n_rows; const uword N_gaus = x.means.n_cols; access::rw(fcovs).zeros(N_dims,N_dims,N_gaus); for(uword g=0; g < N_gaus; ++g) { Mat& fcov = access::rw(fcovs).slice(g); const eT* dcov_mem = x.dcovs.colptr(g); for(uword d=0; d < N_dims; ++d) { fcov.at(d,d) = dcov_mem[d]; } } init_constants(); } template inline void gmm_full::init(const uword in_n_dims, const uword in_n_gaus) { arma_extra_debug_sigprint(); access::rw(means).zeros(in_n_dims, in_n_gaus); access::rw(fcovs).zeros(in_n_dims, in_n_dims, in_n_gaus); for(uword g=0; g < in_n_gaus; ++g) { access::rw(fcovs).slice(g).diag().ones(); } access::rw(hefts).set_size(in_n_gaus); access::rw(hefts).fill(eT(1) / eT(in_n_gaus)); init_constants(); } template inline void gmm_full::init_constants(const bool calc_chol) { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; const eT tmp = (eT(N_dims)/eT(2)) * std::log(Datum::tau); // inv_fcovs.copy_size(fcovs); log_det_etc.set_size(N_gaus); Mat tmp_inv; for(uword g=0; g < N_gaus; ++g) { const Mat& fcov = fcovs.slice(g); Mat& inv_fcov = inv_fcovs.slice(g); //const bool inv_ok = auxlib::inv(tmp_inv, fcov); const bool inv_ok = auxlib::inv_sympd(tmp_inv, fcov); eT log_det_val = eT(0); eT log_det_sign = eT(0); const bool log_det_status = log_det(log_det_val, log_det_sign, fcov); const bool log_det_ok = ( log_det_status && (arma_isfinite(log_det_val)) && (log_det_sign > eT(0)) ); if(inv_ok && log_det_ok) { inv_fcov = tmp_inv; } else { // last resort: treat the covariance matrix as diagonal inv_fcov.zeros(); log_det_val = eT(0); for(uword d=0; d < N_dims; ++d) { const eT sanitised_val = (std::max)( eT(fcov.at(d,d)), eT(std::numeric_limits::min()) ); inv_fcov.at(d,d) = eT(1) / sanitised_val; log_det_val += std::log(sanitised_val); } } log_det_etc[g] = eT(-1) * ( tmp + eT(0.5) * log_det_val ); } // eT* hefts_mem = access::rw(hefts).memptr(); for(uword g=0; g < N_gaus; ++g) { hefts_mem[g] = (std::max)( hefts_mem[g], std::numeric_limits::min() ); } log_hefts = log(hefts); if(calc_chol) { chol_fcovs.copy_size(fcovs); Mat tmp_chol; for(uword g=0; g < N_gaus; ++g) { const Mat& fcov = fcovs.slice(g); Mat& chol_fcov = chol_fcovs.slice(g); const uword chol_layout = 1; // indicates "lower" const bool chol_ok = op_chol::apply_direct(tmp_chol, fcov, chol_layout); if(chol_ok) { chol_fcov = tmp_chol; } else { // last resort: treat the covariance matrix as diagonal chol_fcov.zeros(); for(uword d=0; d < N_dims; ++d) { const eT sanitised_val = (std::max)( eT(fcov.at(d,d)), eT(std::numeric_limits::min()) ); chol_fcov.at(d,d) = std::sqrt(sanitised_val); } } } } } template inline umat gmm_full::internal_gen_boundaries(const uword N) const { arma_extra_debug_sigprint(); #if defined(ARMA_USE_OPENMP) const uword n_threads_avail = uword(omp_get_max_threads()); const uword n_threads = (n_threads_avail > 0) ? ( (n_threads_avail <= N) ? n_threads_avail : 1 ) : 1; #else static constexpr uword n_threads = 1; #endif // get_cout_stream() << "gmm_full::internal_gen_boundaries(): n_threads: " << n_threads << '\n'; umat boundaries(2, n_threads, arma_nozeros_indicator()); if(N > 0) { const uword chunk_size = N / n_threads; uword count = 0; for(uword t=0; t inline eT gmm_full::internal_scalar_log_p(const eT* x) const { arma_extra_debug_sigprint(); const eT* log_hefts_mem = log_hefts.mem; const uword N_gaus = means.n_cols; if(N_gaus > 0) { eT log_sum = internal_scalar_log_p(x, 0) + log_hefts_mem[0]; for(uword g=1; g < N_gaus; ++g) { const eT log_val = internal_scalar_log_p(x, g) + log_hefts_mem[g]; log_sum = log_add_exp(log_sum, log_val); } return log_sum; } else { return -Datum::inf; } } template inline eT gmm_full::internal_scalar_log_p(const eT* x, const uword g) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const eT* mean_mem = means.colptr(g); eT outer_acc = eT(0); const eT* inv_fcov_coldata = inv_fcovs.slice(g).memptr(); for(uword i=0; i < N_dims; ++i) { eT inner_acc = eT(0); for(uword j=0; j < N_dims; ++j) { inner_acc += (x[j] - mean_mem[j]) * inv_fcov_coldata[j]; } inv_fcov_coldata += N_dims; outer_acc += inner_acc * (x[i] - mean_mem[i]); } return eT(-0.5)*outer_acc + log_det_etc.mem[g]; } template inline Row gmm_full::internal_vec_log_p(const Mat& X) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_samples = X.n_cols; arma_debug_check( (X.n_rows != N_dims), "gmm_full::log_p(): incompatible dimensions" ); Row out(N_samples, arma_nozeros_indicator()); if(N_samples > 0) { #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(N_samples); const uword n_threads = boundaries.n_cols; #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); eT* out_mem = out.memptr(); for(uword i=start_index; i <= end_index; ++i) { out_mem[i] = internal_scalar_log_p( X.colptr(i) ); } } } #else { eT* out_mem = out.memptr(); for(uword i=0; i < N_samples; ++i) { out_mem[i] = internal_scalar_log_p( X.colptr(i) ); } } #endif } return out; } template inline Row gmm_full::internal_vec_log_p(const Mat& X, const uword gaus_id) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_samples = X.n_cols; arma_debug_check( (X.n_rows != N_dims), "gmm_full::log_p(): incompatible dimensions" ); arma_debug_check( (gaus_id >= means.n_cols), "gmm_full::log_p(): specified gaussian is out of range" ); Row out(N_samples, arma_nozeros_indicator()); if(N_samples > 0) { #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(N_samples); const uword n_threads = boundaries.n_cols; #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); eT* out_mem = out.memptr(); for(uword i=start_index; i <= end_index; ++i) { out_mem[i] = internal_scalar_log_p( X.colptr(i), gaus_id ); } } } #else { eT* out_mem = out.memptr(); for(uword i=0; i < N_samples; ++i) { out_mem[i] = internal_scalar_log_p( X.colptr(i), gaus_id ); } } #endif } return out; } template inline eT gmm_full::internal_sum_log_p(const Mat& X) const { arma_extra_debug_sigprint(); arma_debug_check( (X.n_rows != means.n_rows), "gmm_full::sum_log_p(): incompatible dimensions" ); const uword N = X.n_cols; if(N == 0) { return (-Datum::inf); } #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(N); const uword n_threads = boundaries.n_cols; Col t_accs(n_threads, arma_zeros_indicator()); #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); eT t_acc = eT(0); for(uword i=start_index; i <= end_index; ++i) { t_acc += internal_scalar_log_p( X.colptr(i) ); } t_accs[t] = t_acc; } return eT(accu(t_accs)); } #else { eT acc = eT(0); for(uword i=0; i inline eT gmm_full::internal_sum_log_p(const Mat& X, const uword gaus_id) const { arma_extra_debug_sigprint(); arma_debug_check( (X.n_rows != means.n_rows), "gmm_full::sum_log_p(): incompatible dimensions" ); arma_debug_check( (gaus_id >= means.n_cols), "gmm_full::sum_log_p(): specified gaussian is out of range" ); const uword N = X.n_cols; if(N == 0) { return (-Datum::inf); } #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(N); const uword n_threads = boundaries.n_cols; Col t_accs(n_threads, arma_zeros_indicator()); #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); eT t_acc = eT(0); for(uword i=start_index; i <= end_index; ++i) { t_acc += internal_scalar_log_p( X.colptr(i), gaus_id ); } t_accs[t] = t_acc; } return eT(accu(t_accs)); } #else { eT acc = eT(0); for(uword i=0; i inline eT gmm_full::internal_avg_log_p(const Mat& X) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_samples = X.n_cols; arma_debug_check( (X.n_rows != N_dims), "gmm_full::avg_log_p(): incompatible dimensions" ); if(N_samples == 0) { return (-Datum::inf); } #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(N_samples); const uword n_threads = boundaries.n_cols; field< running_mean_scalar > t_running_means(n_threads); #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); running_mean_scalar& current_running_mean = t_running_means[t]; for(uword i=start_index; i <= end_index; ++i) { current_running_mean( internal_scalar_log_p( X.colptr(i) ) ); } } eT avg = eT(0); for(uword t=0; t < n_threads; ++t) { running_mean_scalar& current_running_mean = t_running_means[t]; const eT w = eT(current_running_mean.count()) / eT(N_samples); avg += w * current_running_mean.mean(); } return avg; } #else { running_mean_scalar running_mean; for(uword i=0; i < N_samples; ++i) { running_mean( internal_scalar_log_p( X.colptr(i) ) ); } return running_mean.mean(); } #endif } template inline eT gmm_full::internal_avg_log_p(const Mat& X, const uword gaus_id) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_samples = X.n_cols; arma_debug_check( (X.n_rows != N_dims), "gmm_full::avg_log_p(): incompatible dimensions" ); arma_debug_check( (gaus_id >= means.n_cols), "gmm_full::avg_log_p(): specified gaussian is out of range" ); if(N_samples == 0) { return (-Datum::inf); } #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(N_samples); const uword n_threads = boundaries.n_cols; field< running_mean_scalar > t_running_means(n_threads); #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); running_mean_scalar& current_running_mean = t_running_means[t]; for(uword i=start_index; i <= end_index; ++i) { current_running_mean( internal_scalar_log_p( X.colptr(i), gaus_id) ); } } eT avg = eT(0); for(uword t=0; t < n_threads; ++t) { running_mean_scalar& current_running_mean = t_running_means[t]; const eT w = eT(current_running_mean.count()) / eT(N_samples); avg += w * current_running_mean.mean(); } return avg; } #else { running_mean_scalar running_mean; for(uword i=0; i inline uword gmm_full::internal_scalar_assign(const Mat& X, const gmm_dist_mode& dist_mode) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; arma_debug_check( (X.n_rows != N_dims), "gmm_full::assign(): incompatible dimensions" ); arma_debug_check( (N_gaus == 0), "gmm_full::assign(): model has no means" ); const eT* X_mem = X.colptr(0); if(dist_mode == eucl_dist) { eT best_dist = Datum::inf; uword best_g = 0; for(uword g=0; g < N_gaus; ++g) { const eT tmp_dist = distance::eval(N_dims, X_mem, means.colptr(g), X_mem); if(tmp_dist <= best_dist) { best_dist = tmp_dist; best_g = g; } } return best_g; } else if(dist_mode == prob_dist) { const eT* log_hefts_mem = log_hefts.memptr(); eT best_p = -Datum::inf; uword best_g = 0; for(uword g=0; g < N_gaus; ++g) { const eT tmp_p = internal_scalar_log_p(X_mem, g) + log_hefts_mem[g]; if(tmp_p >= best_p) { best_p = tmp_p; best_g = g; } } return best_g; } else { arma_debug_check(true, "gmm_full::assign(): unsupported distance mode"); } return uword(0); } template inline void gmm_full::internal_vec_assign(urowvec& out, const Mat& X, const gmm_dist_mode& dist_mode) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; arma_debug_check( (X.n_rows != N_dims), "gmm_full::assign(): incompatible dimensions" ); const uword X_n_cols = (N_gaus > 0) ? X.n_cols : 0; out.set_size(1,X_n_cols); uword* out_mem = out.memptr(); if(dist_mode == eucl_dist) { #if defined(ARMA_USE_OPENMP) { #pragma omp parallel for schedule(static) for(uword i=0; i::inf; uword best_g = 0; for(uword g=0; g::eval(N_dims, X_colptr, means.colptr(g), X_colptr); if(tmp_dist <= best_dist) { best_dist = tmp_dist; best_g = g; } } out_mem[i] = best_g; } } #else { for(uword i=0; i::inf; uword best_g = 0; for(uword g=0; g::eval(N_dims, X_colptr, means.colptr(g), X_colptr); if(tmp_dist <= best_dist) { best_dist = tmp_dist; best_g = g; } } out_mem[i] = best_g; } } #endif } else if(dist_mode == prob_dist) { #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(X_n_cols); const uword n_threads = boundaries.n_cols; const eT* log_hefts_mem = log_hefts.memptr(); #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); for(uword i=start_index; i <= end_index; ++i) { const eT* X_colptr = X.colptr(i); eT best_p = -Datum::inf; uword best_g = 0; for(uword g=0; g= best_p) { best_p = tmp_p; best_g = g; } } out_mem[i] = best_g; } } } #else { const eT* log_hefts_mem = log_hefts.memptr(); for(uword i=0; i::inf; uword best_g = 0; for(uword g=0; g= best_p) { best_p = tmp_p; best_g = g; } } out_mem[i] = best_g; } } #endif } else { arma_debug_check(true, "gmm_full::assign(): unsupported distance mode"); } } template inline void gmm_full::internal_raw_hist(urowvec& hist, const Mat& X, const gmm_dist_mode& dist_mode) const { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; const uword X_n_cols = X.n_cols; hist.zeros(N_gaus); if(N_gaus == 0) { return; } #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(X_n_cols); const uword n_threads = boundaries.n_cols; field thread_hist(n_threads); for(uword t=0; t < n_threads; ++t) { thread_hist(t).zeros(N_gaus); } if(dist_mode == eucl_dist) { #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { uword* thread_hist_mem = thread_hist(t).memptr(); const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); for(uword i=start_index; i <= end_index; ++i) { const eT* X_colptr = X.colptr(i); eT best_dist = Datum::inf; uword best_g = 0; for(uword g=0; g < N_gaus; ++g) { const eT tmp_dist = distance::eval(N_dims, X_colptr, means.colptr(g), X_colptr); if(tmp_dist <= best_dist) { best_dist = tmp_dist; best_g = g; } } thread_hist_mem[best_g]++; } } } else if(dist_mode == prob_dist) { const eT* log_hefts_mem = log_hefts.memptr(); #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { uword* thread_hist_mem = thread_hist(t).memptr(); const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); for(uword i=start_index; i <= end_index; ++i) { const eT* X_colptr = X.colptr(i); eT best_p = -Datum::inf; uword best_g = 0; for(uword g=0; g < N_gaus; ++g) { const eT tmp_p = internal_scalar_log_p(X_colptr, g) + log_hefts_mem[g]; if(tmp_p >= best_p) { best_p = tmp_p; best_g = g; } } thread_hist_mem[best_g]++; } } } // reduction for(uword t=0; t < n_threads; ++t) { hist += thread_hist(t); } } #else { uword* hist_mem = hist.memptr(); if(dist_mode == eucl_dist) { for(uword i=0; i::inf; uword best_g = 0; for(uword g=0; g < N_gaus; ++g) { const eT tmp_dist = distance::eval(N_dims, X_colptr, means.colptr(g), X_colptr); if(tmp_dist <= best_dist) { best_dist = tmp_dist; best_g = g; } } hist_mem[best_g]++; } } else if(dist_mode == prob_dist) { const eT* log_hefts_mem = log_hefts.memptr(); for(uword i=0; i::inf; uword best_g = 0; for(uword g=0; g < N_gaus; ++g) { const eT tmp_p = internal_scalar_log_p(X_colptr, g) + log_hefts_mem[g]; if(tmp_p >= best_p) { best_p = tmp_p; best_g = g; } } hist_mem[best_g]++; } } } #endif } template template inline void gmm_full::generate_initial_means(const Mat& X, const gmm_seed_mode& seed_mode) { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; if( (seed_mode == static_subset) || (seed_mode == random_subset) ) { uvec initial_indices; if(seed_mode == static_subset) { initial_indices = linspace(0, X.n_cols-1, N_gaus); } else if(seed_mode == random_subset) { initial_indices = randperm(X.n_cols, N_gaus); } // initial_indices.print("initial_indices:"); access::rw(means) = X.cols(initial_indices); } else if( (seed_mode == static_spread) || (seed_mode == random_spread) ) { // going through all of the samples can be extremely time consuming; // instead, if there are enough samples, randomly choose samples with probability 0.1 const bool use_sampling = ((X.n_cols/uword(100)) > N_gaus); const uword step = (use_sampling) ? uword(10) : uword(1); uword start_index = 0; if(seed_mode == static_spread) { start_index = X.n_cols / 2; } else if(seed_mode == random_spread) { start_index = as_scalar(randi(1, distr_param(0,X.n_cols-1))); } access::rw(means).col(0) = X.unsafe_col(start_index); const eT* mah_aux_mem = mah_aux.memptr(); running_stat rs; for(uword g=1; g < N_gaus; ++g) { eT max_dist = eT(0); uword best_i = uword(0); uword start_i = uword(0); if(use_sampling) { uword start_i_proposed = uword(0); if(seed_mode == static_spread) { start_i_proposed = g % uword(10); } if(seed_mode == random_spread) { start_i_proposed = as_scalar(randi(1, distr_param(0,9))); } if(start_i_proposed < X.n_cols) { start_i = start_i_proposed; } } for(uword i=start_i; i < X.n_cols; i += step) { rs.reset(); const eT* X_colptr = X.colptr(i); bool ignore_i = false; // find the average distance between sample i and the means so far for(uword h = 0; h < g; ++h) { const eT dist = distance::eval(N_dims, X_colptr, means.colptr(h), mah_aux_mem); // ignore sample already selected as a mean if(dist == eT(0)) { ignore_i = true; break; } else { rs(dist); } } if( (rs.mean() >= max_dist) && (ignore_i == false)) { max_dist = eT(rs.mean()); best_i = i; } } // set the mean to the sample that is the furthest away from the means so far access::rw(means).col(g) = X.unsafe_col(best_i); } } // get_cout_stream() << "generate_initial_means():" << '\n'; // means.print(); } template template inline void gmm_full::generate_initial_params(const Mat& X, const eT var_floor) { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; const eT* mah_aux_mem = mah_aux.memptr(); const uword X_n_cols = X.n_cols; if(X_n_cols == 0) { return; } // as the covariances are calculated via accumulators, // the means also need to be calculated via accumulators to ensure numerical consistency Mat acc_means(N_dims, N_gaus); Mat acc_dcovs(N_dims, N_gaus); Row acc_hefts(N_gaus, arma_zeros_indicator()); uword* acc_hefts_mem = acc_hefts.memptr(); #if defined(ARMA_USE_OPENMP) { const umat boundaries = internal_gen_boundaries(X_n_cols); const uword n_threads = boundaries.n_cols; field< Mat > t_acc_means(n_threads); field< Mat > t_acc_dcovs(n_threads); field< Row > t_acc_hefts(n_threads); for(uword t=0; t < n_threads; ++t) { t_acc_means(t).zeros(N_dims, N_gaus); t_acc_dcovs(t).zeros(N_dims, N_gaus); t_acc_hefts(t).zeros(N_gaus); } #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { uword* t_acc_hefts_mem = t_acc_hefts(t).memptr(); const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); for(uword i=start_index; i <= end_index; ++i) { const eT* X_colptr = X.colptr(i); eT min_dist = Datum::inf; uword best_g = 0; for(uword g=0; g::eval(N_dims, X_colptr, means.colptr(g), mah_aux_mem); if(dist < min_dist) { min_dist = dist; best_g = g; } } eT* t_acc_mean = t_acc_means(t).colptr(best_g); eT* t_acc_dcov = t_acc_dcovs(t).colptr(best_g); for(uword d=0; d::inf; uword best_g = 0; for(uword g=0; g::eval(N_dims, X_colptr, means.colptr(g), mah_aux_mem); if(dist < min_dist) { min_dist = dist; best_g = g; } } eT* acc_mean = acc_means.colptr(best_g); eT* acc_dcov = acc_dcovs.colptr(best_g); for(uword d=0; d& fcov = access::rw(fcovs).slice(g); fcov.zeros(); for(uword d=0; d= 1) ? tmp : eT(0); fcov.at(d,d) = (acc_heft >= 2) ? eT((acc_dcov[d] / eT(acc_heft)) - (tmp*tmp)) : eT(var_floor); } hefts_mem[g] = eT(acc_heft) / eT(X_n_cols); } em_fix_params(var_floor); } //! multi-threaded implementation of k-means, inspired by MapReduce template template inline bool gmm_full::km_iterate(const Mat& X, const uword max_iter, const bool verbose) { arma_extra_debug_sigprint(); if(verbose) { get_cout_stream().unsetf(ios::showbase); get_cout_stream().unsetf(ios::uppercase); get_cout_stream().unsetf(ios::showpos); get_cout_stream().unsetf(ios::scientific); get_cout_stream().setf(ios::right); get_cout_stream().setf(ios::fixed); } const uword X_n_cols = X.n_cols; if(X_n_cols == 0) { return true; } const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; const eT* mah_aux_mem = mah_aux.memptr(); Mat acc_means(N_dims, N_gaus, arma_zeros_indicator()); Row acc_hefts( N_gaus, arma_zeros_indicator()); Row last_indx( N_gaus, arma_zeros_indicator()); Mat new_means = means; Mat old_means = means; running_mean_scalar rs_delta; #if defined(ARMA_USE_OPENMP) const umat boundaries = internal_gen_boundaries(X_n_cols); const uword n_threads = boundaries.n_cols; field< Mat > t_acc_means(n_threads); field< Row > t_acc_hefts(n_threads); field< Row > t_last_indx(n_threads); #else const uword n_threads = 1; #endif if(verbose) { get_cout_stream() << "gmm_full::learn(): k-means: n_threads: " << n_threads << '\n'; get_cout_stream().flush(); } for(uword iter=1; iter <= max_iter; ++iter) { #if defined(ARMA_USE_OPENMP) { for(uword t=0; t < n_threads; ++t) { t_acc_means(t).zeros(N_dims, N_gaus); t_acc_hefts(t).zeros(N_gaus); t_last_indx(t).zeros(N_gaus); } #pragma omp parallel for schedule(static) for(uword t=0; t < n_threads; ++t) { Mat& t_acc_means_t = t_acc_means(t); uword* t_acc_hefts_mem = t_acc_hefts(t).memptr(); uword* t_last_indx_mem = t_last_indx(t).memptr(); const uword start_index = boundaries.at(0,t); const uword end_index = boundaries.at(1,t); for(uword i=start_index; i <= end_index; ++i) { const eT* X_colptr = X.colptr(i); eT min_dist = Datum::inf; uword best_g = 0; for(uword g=0; g::eval(N_dims, X_colptr, old_means.colptr(g), mah_aux_mem); if(dist < min_dist) { min_dist = dist; best_g = g; } } eT* t_acc_mean = t_acc_means_t.colptr(best_g); for(uword d=0; d= 1 ) { last_indx(g) = t_last_indx(t)(g); } } } #else { acc_hefts.zeros(); acc_means.zeros(); last_indx.zeros(); uword* acc_hefts_mem = acc_hefts.memptr(); uword* last_indx_mem = last_indx.memptr(); for(uword i=0; i < X_n_cols; ++i) { const eT* X_colptr = X.colptr(i); eT min_dist = Datum::inf; uword best_g = 0; for(uword g=0; g::eval(N_dims, X_colptr, old_means.colptr(g), mah_aux_mem); if(dist < min_dist) { min_dist = dist; best_g = g; } } eT* acc_mean = acc_means.colptr(best_g); for(uword d=0; d= 1) ? (acc_mean[d] / eT(acc_heft)) : eT(0); } } // heuristics to resurrect dead means const uvec dead_gs = find(acc_hefts == uword(0)); if(dead_gs.n_elem > 0) { if(verbose) { get_cout_stream() << "gmm_full::learn(): k-means: recovering from dead means\n"; get_cout_stream().flush(); } uword* last_indx_mem = last_indx.memptr(); const uvec live_gs = sort( find(acc_hefts >= uword(2)), "descend" ); if(live_gs.n_elem == 0) { return false; } uword live_gs_count = 0; for(uword dead_gs_count = 0; dead_gs_count < dead_gs.n_elem; ++dead_gs_count) { const uword dead_g_id = dead_gs(dead_gs_count); uword proposed_i = 0; if(live_gs_count < live_gs.n_elem) { const uword live_g_id = live_gs(live_gs_count); ++live_gs_count; if(live_g_id == dead_g_id) { return false; } // recover by using a sample from a known good mean proposed_i = last_indx_mem[live_g_id]; } else { // recover by using a randomly seleced sample (last resort) proposed_i = as_scalar(randi(1, distr_param(0,X_n_cols-1))); } if(proposed_i >= X_n_cols) { return false; } new_means.col(dead_g_id) = X.col(proposed_i); } } rs_delta.reset(); for(uword g=0; g < N_gaus; ++g) { rs_delta( distance::eval(N_dims, old_means.colptr(g), new_means.colptr(g), mah_aux_mem) ); } if(verbose) { get_cout_stream() << "gmm_full::learn(): k-means: iteration: "; get_cout_stream().unsetf(ios::scientific); get_cout_stream().setf(ios::fixed); get_cout_stream().width(std::streamsize(4)); get_cout_stream() << iter; get_cout_stream() << " delta: "; get_cout_stream().unsetf(ios::fixed); //get_cout_stream().setf(ios::scientific); get_cout_stream() << rs_delta.mean() << '\n'; get_cout_stream().flush(); } arma::swap(old_means, new_means); if(rs_delta.mean() <= Datum::eps) { break; } } access::rw(means) = old_means; if(means.internal_has_nonfinite()) { return false; } return true; } //! multi-threaded implementation of Expectation-Maximisation, inspired by MapReduce template inline bool gmm_full::em_iterate(const Mat& X, const uword max_iter, const eT var_floor, const bool verbose) { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; if(verbose) { get_cout_stream().unsetf(ios::showbase); get_cout_stream().unsetf(ios::uppercase); get_cout_stream().unsetf(ios::showpos); get_cout_stream().unsetf(ios::scientific); get_cout_stream().setf(ios::right); get_cout_stream().setf(ios::fixed); } const umat boundaries = internal_gen_boundaries(X.n_cols); const uword n_threads = boundaries.n_cols; field< Mat > t_acc_means(n_threads); field< Cube > t_acc_fcovs(n_threads); field< Col > t_acc_norm_lhoods(n_threads); field< Col > t_gaus_log_lhoods(n_threads); Col t_progress_log_lhood(n_threads, arma_nozeros_indicator()); for(uword t=0; t::inf; const bool calc_chol = false; for(uword iter=1; iter <= max_iter; ++iter) { init_constants(calc_chol); em_update_params(X, boundaries, t_acc_means, t_acc_fcovs, t_acc_norm_lhoods, t_gaus_log_lhoods, t_progress_log_lhood, var_floor); em_fix_params(var_floor); const eT new_avg_log_p = accu(t_progress_log_lhood) / eT(t_progress_log_lhood.n_elem); if(verbose) { get_cout_stream() << "gmm_full::learn(): EM: iteration: "; get_cout_stream().unsetf(ios::scientific); get_cout_stream().setf(ios::fixed); get_cout_stream().width(std::streamsize(4)); get_cout_stream() << iter; get_cout_stream() << " avg_log_p: "; get_cout_stream().unsetf(ios::fixed); //get_cout_stream().setf(ios::scientific); get_cout_stream() << new_avg_log_p << '\n'; get_cout_stream().flush(); } if(arma_isfinite(new_avg_log_p) == false) { return false; } if(std::abs(old_avg_log_p - new_avg_log_p) <= Datum::eps) { break; } old_avg_log_p = new_avg_log_p; } for(uword g=0; g < N_gaus; ++g) { const Mat& fcov = fcovs.slice(g); if(any(vectorise(fcov.diag()) <= eT(0))) { return false; } } if(means.internal_has_nonfinite()) { return false; } if(fcovs.internal_has_nonfinite()) { return false; } if(hefts.internal_has_nonfinite()) { return false; } return true; } template inline void gmm_full::em_update_params ( const Mat& X, const umat& boundaries, field< Mat >& t_acc_means, field< Cube >& t_acc_fcovs, field< Col >& t_acc_norm_lhoods, field< Col >& t_gaus_log_lhoods, Col& t_progress_log_lhood, const eT var_floor ) { arma_extra_debug_sigprint(); const uword n_threads = boundaries.n_cols; // em_generate_acc() is the "map" operation, which produces partial accumulators for means, diagonal covariances and hefts #if defined(ARMA_USE_OPENMP) { #pragma omp parallel for schedule(static) for(uword t=0; t& acc_means = t_acc_means[t]; Cube& acc_fcovs = t_acc_fcovs[t]; Col& acc_norm_lhoods = t_acc_norm_lhoods[t]; Col& gaus_log_lhoods = t_gaus_log_lhoods[t]; eT& progress_log_lhood = t_progress_log_lhood[t]; em_generate_acc(X, boundaries.at(0,t), boundaries.at(1,t), acc_means, acc_fcovs, acc_norm_lhoods, gaus_log_lhoods, progress_log_lhood); } } #else { em_generate_acc(X, boundaries.at(0,0), boundaries.at(1,0), t_acc_means[0], t_acc_fcovs[0], t_acc_norm_lhoods[0], t_gaus_log_lhoods[0], t_progress_log_lhood[0]); } #endif const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; Mat& final_acc_means = t_acc_means[0]; Cube& final_acc_fcovs = t_acc_fcovs[0]; Col& final_acc_norm_lhoods = t_acc_norm_lhoods[0]; // the "reduce" operation, which combines the partial accumulators produced by the separate threads for(uword t=1; t mean_outer(N_dims, N_dims, arma_nozeros_indicator()); //// update each component without sanity checking //for(uword g=0; g < N_gaus; ++g) // { // const eT acc_norm_lhood = (std::max)( final_acc_norm_lhoods[g], std::numeric_limits::min() ); // // hefts_mem[g] = acc_norm_lhood / eT(X.n_cols); // // eT* mean_mem = access::rw(means).colptr(g); // eT* acc_mean_mem = final_acc_means.colptr(g); // // for(uword d=0; d < N_dims; ++d) // { // mean_mem[d] = acc_mean_mem[d] / acc_norm_lhood; // } // // const Col mean(mean_mem, N_dims, false, true); // // mean_outer = mean * mean.t(); // // Mat& fcov = access::rw(fcovs).slice(g); // Mat& acc_fcov = final_acc_fcovs.slice(g); // // fcov = acc_fcov / acc_norm_lhood - mean_outer; // } // conditionally update each component; if only a subset of the hefts was updated, em_fix_params() will sanitise them for(uword g=0; g < N_gaus; ++g) { const eT acc_norm_lhood = (std::max)( final_acc_norm_lhoods[g], std::numeric_limits::min() ); if(arma_isfinite(acc_norm_lhood) == false) { continue; } eT* acc_mean_mem = final_acc_means.colptr(g); for(uword d=0; d < N_dims; ++d) { acc_mean_mem[d] /= acc_norm_lhood; } const Col new_mean(acc_mean_mem, N_dims, false, true); mean_outer = new_mean * new_mean.t(); Mat& acc_fcov = final_acc_fcovs.slice(g); acc_fcov /= acc_norm_lhood; acc_fcov -= mean_outer; for(uword d=0; d < N_dims; ++d) { eT& val = acc_fcov.at(d,d); if(val < var_floor) { val = var_floor; } } if(acc_fcov.internal_has_nonfinite()) { continue; } eT log_det_val = eT(0); eT log_det_sign = eT(0); const bool log_det_status = log_det(log_det_val, log_det_sign, acc_fcov); const bool log_det_ok = ( log_det_status && (arma_isfinite(log_det_val)) && (log_det_sign > eT(0)) ); const bool inv_ok = (log_det_ok) ? bool(auxlib::inv_sympd(mean_outer, acc_fcov)) : bool(false); // mean_outer is used as a junk matrix if(log_det_ok && inv_ok) { hefts_mem[g] = acc_norm_lhood / eT(X.n_cols); eT* mean_mem = access::rw(means).colptr(g); for(uword d=0; d < N_dims; ++d) { mean_mem[d] = acc_mean_mem[d]; } Mat& fcov = access::rw(fcovs).slice(g); fcov = acc_fcov; } } } template inline void gmm_full::em_generate_acc ( const Mat& X, const uword start_index, const uword end_index, Mat& acc_means, Cube& acc_fcovs, Col& acc_norm_lhoods, Col& gaus_log_lhoods, eT& progress_log_lhood ) const { arma_extra_debug_sigprint(); progress_log_lhood = eT(0); acc_means.zeros(); acc_fcovs.zeros(); acc_norm_lhoods.zeros(); gaus_log_lhoods.zeros(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; const eT* log_hefts_mem = log_hefts.memptr(); eT* gaus_log_lhoods_mem = gaus_log_lhoods.memptr(); for(uword i=start_index; i <= end_index; i++) { const eT* x = X.colptr(i); for(uword g=0; g < N_gaus; ++g) { gaus_log_lhoods_mem[g] = internal_scalar_log_p(x, g) + log_hefts_mem[g]; } eT log_lhood_sum = gaus_log_lhoods_mem[0]; for(uword g=1; g < N_gaus; ++g) { log_lhood_sum = log_add_exp(log_lhood_sum, gaus_log_lhoods_mem[g]); } progress_log_lhood += log_lhood_sum; for(uword g=0; g < N_gaus; ++g) { const eT norm_lhood = std::exp(gaus_log_lhoods_mem[g] - log_lhood_sum); acc_norm_lhoods[g] += norm_lhood; eT* acc_mean_mem = acc_means.colptr(g); for(uword d=0; d < N_dims; ++d) { acc_mean_mem[d] += x[d] * norm_lhood; } Mat& acc_fcov = access::rw(acc_fcovs).slice(g); // specialised version of acc_fcov += norm_lhood * (xx * xx.t()); for(uword d=0; d < N_dims; ++d) { const uword dp1 = d+1; const eT xd = x[d]; eT* acc_fcov_col_d = acc_fcov.colptr(d) + d; eT* acc_fcov_row_d = &(acc_fcov.at(d,dp1)); (*acc_fcov_col_d) += norm_lhood * (xd * xd); acc_fcov_col_d++; for(uword e=dp1; e < N_dims; ++e) { const eT val = norm_lhood * (xd * x[e]); (*acc_fcov_col_d) += val; acc_fcov_col_d++; (*acc_fcov_row_d) += val; acc_fcov_row_d += N_dims; } } } } progress_log_lhood /= eT((end_index - start_index) + 1); } template inline void gmm_full::em_fix_params(const eT var_floor) { arma_extra_debug_sigprint(); const uword N_dims = means.n_rows; const uword N_gaus = means.n_cols; const eT var_ceiling = std::numeric_limits::max(); for(uword g=0; g < N_gaus; ++g) { Mat& fcov = access::rw(fcovs).slice(g); for(uword d=0; d < N_dims; ++d) { eT& var_val = fcov.at(d,d); if(var_val < var_floor ) { var_val = var_floor; } else if(var_val > var_ceiling) { var_val = var_ceiling; } else if(arma_isnan(var_val) ) { var_val = eT(1); } } } eT* hefts_mem = access::rw(hefts).memptr(); for(uword g1=0; g1 < N_gaus; ++g1) { if(hefts_mem[g1] > eT(0)) { const eT* means_colptr_g1 = means.colptr(g1); for(uword g2=(g1+1); g2 < N_gaus; ++g2) { if( (hefts_mem[g2] > eT(0)) && (std::abs(hefts_mem[g1] - hefts_mem[g2]) <= std::numeric_limits::epsilon()) ) { const eT dist = distance::eval(N_dims, means_colptr_g1, means.colptr(g2), means_colptr_g1); if(dist == eT(0)) { hefts_mem[g2] = eT(0); } } } } } const eT heft_floor = std::numeric_limits::min(); const eT heft_initial = eT(1) / eT(N_gaus); for(uword i=0; i < N_gaus; ++i) { eT& heft_val = hefts_mem[i]; if(heft_val < heft_floor) { heft_val = heft_floor; } else if(heft_val > eT(1) ) { heft_val = eT(1); } else if(arma_isnan(heft_val) ) { heft_val = heft_initial; } } const eT heft_sum = accu(hefts); if((heft_sum < (eT(1) - Datum::eps)) || (heft_sum > (eT(1) + Datum::eps))) { access::rw(hefts) /= heft_sum; } } } // namespace gmm_priv //! @}