EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Fitting.h
Go to the documentation of this file.
1#pragma once
2
8
9#include <algorithm>
10#include <array>
11#include <cstddef>
12#include <expected>
13#include <random>
14#include <span>
15#include <utility>
16#include <vector>
17
18#include <Eigen/Cholesky>
19
33
34namespace Emrys
35{
36
45
49template <typename Scalar>
51{
53 int maxIterations = 100;
54
56 Scalar tolerance = Scalar(1e-8);
57
64 Scalar regularization = Scalar(1e-6);
65
67 Scalar minLogWeight = Scalar(-20);
68};
69
73template <typename Space, typename Scalar = double, typename Allocator = DefaultAllocator>
81
82namespace internal
83{
84
86template <typename Scalar, typename Space>
87struct FitSource
88{
89 using MatrixType = typename Moments<Space, Scalar>::MatrixType;
91 Eigen::Array<Scalar, Eigen::Dynamic, 1> weights;
92 std::vector<MatrixType> covs;
93 std::vector<MatrixType> covFactors;
94 Space space;
95};
96
98template <typename Scalar, typename Space, typename Allocator>
99[[nodiscard]] std::expected<FitSource<Scalar, Space>, FitError>
100fitSource(ParticleSet<Space, Scalar, Allocator> const & particles)
101{
102 auto weights = particles.normalizedWeights();
103 if (!weights)
104 {
105 return std::unexpected{FitError::InvalidWeights};
106 }
107 return FitSource<Scalar, Space>{particles.states(), std::move(*weights), {}, {}, particles.space()};
108}
109
113template <typename Scalar, typename Space, typename Allocator>
114[[nodiscard]] FitSource<Scalar, Space>
115fitSource(GaussianMixture<Space, Scalar, Allocator> const & mixture)
116{
117 using MatrixType = typename FitSource<Scalar, Space>::MatrixType;
118 auto source = FitSource<Scalar, Space>{
119 SampleSet<Space, Scalar>{mixture.valueDimension(), mixture.size()},
120 Eigen::Array<Scalar, Eigen::Dynamic, 1>{mixture.size()},
121 {},
122 {},
123 mixture.space()
124 };
125 source.covs.reserve(std::size_t(mixture.size()));
126 source.covFactors.reserve(std::size_t(mixture.size()));
127 Eigen::Index i = 0;
128 for (auto const & entry : mixture)
129 {
130 auto const gaussian = entry.component.toDensity();
131 source.points.col(i) = gaussian->mean();
132 source.weights(i) = Eigen::numext::exp(entry.logWeight - mixture.logTotalWeight());
133 source.covs.push_back(MatrixType{entry.component.covarianceLower().template selfadjointView<Eigen::Lower>()});
134 source.covFactors.push_back(MatrixType{gaussian->llt().matrixL()});
135 ++i;
136 }
137 return source;
138}
139
156template <typename Scalar, typename Space, typename Allocator>
157class MixtureFitter
158{
159public:
160
161 using MomentsType = Moments<Space, Scalar>;
162 using VectorType = typename MomentsType::VectorType;
163 using DifferenceVectorType = typename MomentsType::DifferenceVectorType;
164 using MatrixType = typename MomentsType::MatrixType;
165 using Points = SampleSet<Space, Scalar>;
166 using Deviations = SampleSet<DifferenceSpace<Space>, Scalar>;
167 using Weights = Eigen::Array<Scalar, Eigen::Dynamic, 1>;
168 using Responsibilities = Eigen::Array<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
169 using Source = FitSource<Scalar, Space>;
170 using Result = MixtureFit<Space, Scalar, Allocator>;
171
173 MixtureFitter(Source const & source, FitOptions<Scalar> const & options, Allocator const & allocator)
174 : m_source{source}
175 , m_options{options}
176 , m_allocator{allocator}
177 {
178 EMRYS_FILTER_ASSERT(options.maxIterations >= 1);
179 }
180
182 template <std::uniform_random_bit_generator G>
183 [[nodiscard]] std::expected<Result, FitError>
184 run(Points const & candidates, Weights const & candidateWeights, Eigen::Index k, G & rng)
185 {
186 return run(execution::sequential, candidates, candidateWeights, k, rng);
187 }
188
193 template <ExecutionPolicy P, std::uniform_random_bit_generator G>
194 [[nodiscard]] std::expected<Result, FitError>
195 run(P && policy, Points const & candidates, Weights const & candidateWeights, Eigen::Index k, G & rng)
196 {
197 EMRYS_FILTER_ASSERT(k >= 1);
198 if (!prepareWhitening())
199 {
200 return std::unexpected{FitError::Degenerate};
201 }
202 Responsibilities responsibilities = initialAssignment(seed(candidates, candidateWeights, k, rng));
203 mStep(policy, responsibilities);
204
205 Scalar previous = -Eigen::NumTraits<Scalar>::infinity();
206 for (int iteration = 1;; ++iteration)
207 {
208 auto const logLikelihood = eStep(policy, responsibilities);
209 if (!logLikelihood)
210 {
211 return std::unexpected{logLikelihood.error()};
212 }
213 bool const converged = *logLikelihood - previous <= m_options.tolerance;
214 if (converged || iteration >= m_options.maxIterations)
215 {
216 return result(*logLikelihood, iteration, converged);
217 }
218 previous = *logLikelihood;
219 mStep(policy, responsibilities);
220 }
221 }
222
223private:
224
226 [[nodiscard]] bool
227 prepareWhitening()
228 {
229 MomentsType const moments =
230 weightedMomentsIn<MomentsType>(m_source.space, m_source.points, m_source.weights.log(), Scalar(0));
231 MatrixType total = moments.covarianceLower();
232 for (std::size_t i = 0; i < m_source.covs.size(); ++i)
233 {
234 total += m_source.weights(Eigen::Index(i)) * m_source.covs[i];
235 }
236 DifferenceVectorType const variances = total.diagonal();
237 // A dimension of identical points leaves only rounding noise, of order epsilon² times
238 // its second moment; with no spread there is no scale to fit or to floor against. Where
239 // differences have other coordinates than values, the mean has no part in that scale.
240 DifferenceVectorType const secondMoments = [&] {
241 if constexpr (hasEqualSizes<Space>)
242 {
243 return DifferenceVectorType{variances.array() + moments.mean().array().square()};
244 }
245 else
246 {
247 return variances;
248 }
249 }();
250 if (!(variances.array() > Eigen::NumTraits<Scalar>::epsilon() * secondMoments.array()).all()
251 || !allFinite(variances))
252 {
253 return false;
254 }
255 m_floor = m_options.regularization * variances;
256 total.diagonal() += m_floor;
257 m_whitening.compute(total);
258 return m_whitening.info() == Eigen::Success;
259 }
260
264 template <typename Derived>
265 [[nodiscard]] Weights
266 distancesFrom(Points const & points, Eigen::MatrixBase<Derived> const & center) const
267 {
268 Deviations differences = Emrys::minus(m_source.space, points, center);
269 solveInPlace(m_whitening.matrixL(), differences);
270 return differences.colwise().squaredNorm().transpose().array();
271 }
272
280 template <typename G>
281 [[nodiscard]] Points
282 seed(Points const & candidates, Weights const & weights, Eigen::Index k, G & rng) const
283 {
284 Eigen::Index const m = candidates.cols();
285 Weights distances = Weights::Constant(m, Eigen::NumTraits<Scalar>::infinity());
286 auto probabilities = std::vector<Scalar>(weights.begin(), weights.end());
287 auto chosen = std::vector<Eigen::Index>{};
288 while (true)
289 {
290 auto pick = std::array<Eigen::Index, 1>{};
291 Scalar const u = uniform01<Scalar>(rng);
292 invertCumulative(std::span<Scalar const>{probabilities}, std::span<Eigen::Index>{pick}, [u](std::size_t) {
293 return u;
294 });
295 chosen.push_back(pick[0]);
296 if (Eigen::Index(chosen.size()) == k)
297 {
298 break;
299 }
300 distances = distances.min(distancesFrom(candidates, candidates.col(pick[0])));
301 Scalar total = 0;
302 for (Eigen::Index i = 0; i < m; ++i)
303 {
304 probabilities[std::size_t(i)] = weights(i) * distances(i);
305 total += probabilities[std::size_t(i)];
306 }
307 if (!(total > Scalar(0)))
308 {
309 break;
310 }
311 }
312 auto seeds = Points{candidates.rows(), Eigen::Index(chosen.size())};
313 for (std::size_t s = 0; s < chosen.size(); ++s)
314 {
315 seeds.col(Eigen::Index(s)) = candidates.col(chosen[s]);
316 }
317 return seeds;
318 }
319
321 [[nodiscard]] Responsibilities
322 initialAssignment(Points const & seeds) const
323 {
324 Eigen::Index const n = m_source.points.cols();
325 Weights nearestDistance = Weights::Constant(n, Eigen::NumTraits<Scalar>::infinity());
326 Eigen::Array<Eigen::Index, Eigen::Dynamic, 1> nearest = Eigen::Array<Eigen::Index, Eigen::Dynamic, 1>::Zero(n);
327 for (Eigen::Index s = 0; s < seeds.cols(); ++s)
328 {
329 Weights const distances = distancesFrom(m_source.points, seeds.col(s));
330 for (Eigen::Index i = 0; i < n; ++i)
331 {
332 if (distances(i) < nearestDistance(i))
333 {
334 nearestDistance(i) = distances(i);
335 nearest(i) = s;
336 }
337 }
338 }
339 Responsibilities responsibilities = Responsibilities::Zero(seeds.cols(), n);
340 for (Eigen::Index i = 0; i < n; ++i)
341 {
342 responsibilities(nearest(i), i) = Scalar(1);
343 }
344 return responsibilities;
345 }
346
350 template <typename P>
351 void
352 mStep(P && policy, Responsibilities const & responsibilities)
353 {
354 m_logWeights.clear();
355 m_means.clear();
356 m_covs.clear();
357 for (Eigen::Index k = 0; k < responsibilities.rows(); ++k)
358 {
359 Weights const w = m_source.weights * responsibilities.row(k).transpose();
360 Scalar const mass = w.sum();
361 if (!(mass > Scalar(0)) || Eigen::numext::log(mass) < m_options.minLogWeight)
362 {
363 continue;
364 }
365 MomentsType moments = weightedMomentsIn<MomentsType>(
366 policy,
367 m_source.space,
368 m_source.points,
369 w.log(),
370 Eigen::numext::log(mass)
371 );
372 // The points' own covariances, as reduceRanges() sums them: in order under Sequential.
373 moments.covarianceLower() = reduceRanges(
374 policy,
375 Eigen::Index(m_source.covs.size()),
376 [&](Eigen::Index begin, Eigen::Index length) {
377 MatrixType sum =
378 begin == 0
379 ? moments.covarianceLower()
380 : MatrixType::Zero(moments.covarianceLower().rows(), moments.covarianceLower().cols());
381 for (Eigen::Index i = begin; i < begin + length; ++i)
382 {
383 if (w(i) > Scalar(0))
384 {
385 sum += (w(i) / mass) * m_source.covs[std::size_t(i)];
386 }
387 }
388 return sum;
389 },
390 [](MatrixType a, MatrixType const & b) {
391 a += b;
392 return a;
393 }
394 );
395 moments.covarianceLower().diagonal() += m_floor;
396 m_logWeights.push_back(Eigen::numext::log(mass));
397 m_means.push_back(moments.mean());
398 m_covs.push_back(moments.covarianceLower());
399 }
400 }
401
408 template <typename P>
409 [[nodiscard]] std::expected<Scalar, FitError>
410 eStep(P && policy, Responsibilities & responsibilities)
411 {
412 auto factors = std::vector<Eigen::LLT<MatrixType>>{};
413 auto logDeterminants = std::vector<Scalar>{};
414 factors.reserve(m_covs.size());
415 logDeterminants.reserve(m_covs.size());
416 std::size_t out = 0;
417 for (std::size_t k = 0; k < m_covs.size(); ++k)
418 {
419 auto llt = Eigen::LLT<MatrixType>{m_covs[k]};
420 Scalar const logDeterminant = Scalar(2) * llt.matrixLLT().diagonal().array().log().sum();
421 if (llt.info() != Eigen::Success || !isFinite(logDeterminant))
422 {
423 continue;
424 }
425 m_logWeights[out] = m_logWeights[k];
426 m_means[out] = m_means[k];
427 m_covs[out] = m_covs[k];
428 factors.push_back(std::move(llt));
429 logDeterminants.push_back(logDeterminant);
430 ++out;
431 }
432 m_logWeights.resize(out);
433 m_means.resize(out);
434 m_covs.resize(out);
435 if (out == 0)
436 {
437 return std::unexpected{FitError::Degenerate};
438 }
439
440 Eigen::Index const n = m_source.points.cols();
441 Scalar const log2Pi = Emrys::log2Pi<Scalar>();
442 Scalar const dimension = Scalar(differenceDimension(m_source.space));
443 auto logTerms = Responsibilities{Eigen::Index(out), n};
444 Eigen::Index const withCovariance = Eigen::Index(m_source.covFactors.size());
445 // Under Sequential, one chunk of every point.
446 forEachChunk(policy, n, [&](Eigen::Index begin, Eigen::Index length) {
447 for (std::size_t k = 0; k < out; ++k)
448 {
449 Deviations residuals =
450 Emrys::minus(m_source.space, m_source.points.middleCols(begin, length), m_means[k]);
451 solveInPlace(factors[k].matrixL(), residuals);
452 logTerms.row(Eigen::Index(k)).segment(begin, length) =
453 m_logWeights[k] - Scalar(0.5) * (dimension * log2Pi + logDeterminants[k])
454 - Scalar(0.5) * residuals.colwise().squaredNorm().array();
455 for (Eigen::Index i = begin; i < std::min(begin + length, withCovariance); ++i)
456 {
457 MatrixType solved = m_source.covFactors[std::size_t(i)];
458 solveInPlace(factors[k].matrixL(), solved);
459 logTerms(Eigen::Index(k), i) -= Scalar(0.5) * solved.squaredNorm();
460 }
461 }
462 });
463
464 responsibilities.resize(logTerms.rows(), n);
465 auto contributions = Weights{n};
466 forEachIndex(policy, n, [&](Eigen::Index i) {
467 auto sum = LogSumExp<Scalar>{};
468 for (Eigen::Index k = 0; k < logTerms.rows(); ++k)
469 {
470 sum.add(logTerms(k, i));
471 }
472 Scalar const logNormalizer = sum.value();
473 // A point every component finds impossible (or with no weight) takes no part in the M-step.
474 if (!isFinite(logNormalizer) || !(m_source.weights(i) > Scalar(0)))
475 {
476 responsibilities.col(i).setZero();
477 contributions(i) = Scalar(0);
478 return;
479 }
480 responsibilities.col(i) = (logTerms.col(i) - logNormalizer).exp();
481 contributions(i) = m_source.weights(i) * logNormalizer;
482 });
483 // Summed in order, so that the test for convergence is Sequential's under every policy.
484 Scalar logLikelihood = 0;
485 for (Scalar const contribution : contributions)
486 {
487 logLikelihood += contribution;
488 }
489 return logLikelihood;
490 }
491
493 [[nodiscard]] Result
494 result(Scalar logLikelihood, int iterations, bool converged) const
495 {
496 auto mixture = GaussianMixtureParams<Space, Scalar, Allocator>{m_source.space, m_allocator};
497 mixture.reserve(Eigen::Index(m_means.size()));
498 for (std::size_t k = 0; k < m_means.size(); ++k)
499 {
500 mixture.add(m_logWeights[k], m_means[k], m_covs[k]);
501 }
502 return {std::move(mixture), logLikelihood, iterations, converged};
503 }
504
505 Source const & m_source;
506 FitOptions<Scalar> m_options;
507 Allocator m_allocator;
508 Eigen::LLT<MatrixType> m_whitening;
509 DifferenceVectorType m_floor;
510 std::vector<Scalar> m_logWeights;
511 std::vector<VectorType> m_means;
512 std::vector<MatrixType> m_covs;
513};
514
515} // namespace internal
516
530template <typename Scalar, typename Space, typename Allocator, std::uniform_random_bit_generator G>
531[[nodiscard]] std::expected<MixtureFit<Space, Scalar, Allocator>, FitError>
534 Eigen::Index k,
535 G & rng,
536 FitOptions<Scalar> const & options = {}
537)
538{
539 auto const source = internal::fitSource(particles);
540 if (!source)
541 {
542 return std::unexpected{source.error()};
543 }
544 auto fitter = internal::MixtureFitter<Scalar, Space, Allocator>{*source, options, particles.get_allocator()};
545 return fitter.run(source->points, source->weights, k, rng);
546}
547
559template <typename Scalar, typename Space, typename Allocator, std::uniform_random_bit_generator G>
560[[nodiscard]] std::expected<MixtureFit<Space, Scalar, Allocator>, FitError>
563 Eigen::Index k,
564 G & rng,
565 FitOptions<Scalar> const & options = {}
566)
567{
568 auto const source = internal::fitSource(mixture);
569 auto fitter = internal::MixtureFitter<Scalar, Space, Allocator>{source, options, mixture.get_allocator()};
570 return fitter.run(source.points, source.weights, k, rng);
571}
572
582template <
583 typename Scalar,
584 typename Space,
585 typename Allocator,
586 typename SeedAllocator,
587 std::uniform_random_bit_generator G>
588[[nodiscard]] std::expected<MixtureFit<Space, Scalar, Allocator>, FitError>
592 Eigen::Index k,
593 G & rng,
594 FitOptions<Scalar> const & options = {}
595)
596{
597 EMRYS_FILTER_ASSERT(particles.space() == seedsFrom.space() && "the particles and seeds must share a space");
598 auto const source = internal::fitSource(particles);
599 if (!source)
600 {
601 return std::unexpected{source.error()};
602 }
603 auto const seeds = internal::fitSource(seedsFrom);
604 auto fitter = internal::MixtureFitter<Scalar, Space, Allocator>{*source, options, particles.get_allocator()};
605 return fitter.run(seeds.points, seeds.weights, k, rng);
606}
607
613template <ExecutionPolicy P, typename Scalar, typename Space, typename Allocator, std::uniform_random_bit_generator G>
614[[nodiscard]] std::expected<MixtureFit<Space, Scalar, Allocator>, FitError>
616 P && policy,
618 Eigen::Index k,
619 G & rng,
620 FitOptions<Scalar> const & options = {}
621)
622{
623 auto const source = internal::fitSource(particles);
624 if (!source)
625 {
626 return std::unexpected{source.error()};
627 }
628 auto fitter = internal::MixtureFitter<Scalar, Space, Allocator>{*source, options, particles.get_allocator()};
629 return fitter.run(std::forward<P>(policy), source->points, source->weights, k, rng);
630}
631
636template <ExecutionPolicy P, typename Scalar, typename Space, typename Allocator, std::uniform_random_bit_generator G>
637[[nodiscard]] std::expected<MixtureFit<Space, Scalar, Allocator>, FitError>
639 P && policy,
641 Eigen::Index k,
642 G & rng,
643 FitOptions<Scalar> const & options = {}
644)
645{
646 auto const source = internal::fitSource(mixture);
647 auto fitter = internal::MixtureFitter<Scalar, Space, Allocator>{source, options, mixture.get_allocator()};
648 return fitter.run(std::forward<P>(policy), source.points, source.weights, k, rng);
649}
650
655template <
656 ExecutionPolicy P,
657 typename Scalar,
658 typename Space,
659 typename Allocator,
660 typename SeedAllocator,
661 std::uniform_random_bit_generator G>
662[[nodiscard]] std::expected<MixtureFit<Space, Scalar, Allocator>, FitError>
664 P && policy,
667 Eigen::Index k,
668 G & rng,
669 FitOptions<Scalar> const & options = {}
670)
671{
672 EMRYS_FILTER_ASSERT(particles.space() == seedsFrom.space() && "the particles and seeds must share a space");
673 auto const source = internal::fitSource(particles);
674 if (!source)
675 {
676 return std::unexpected{source.error()};
677 }
678 auto const seeds = internal::fitSource(seedsFrom);
679 auto fitter = internal::MixtureFitter<Scalar, Space, Allocator>{*source, options, particles.get_allocator()};
680 return fitter.run(std::forward<P>(policy), seeds.points, seeds.weights, k, rng);
681}
682
683} // namespace Emrys
684
685#ifndef EMRYS_FILTER_NO_IO
686
687namespace Emrys
688{
689
690namespace internal
691{
692
694inline void
695describe(std::ostream & stream, FitError error, Indent indent)
696{
697 stream << indent;
698 switch (error)
699 {
701 stream << "the source has no spread in some direction";
702 break;
704 stream << "the weights cannot be normalized";
705 break;
706 }
707}
708
709} // namespace internal
710
711} // namespace Emrys
712
713#endif // EMRYS_FILTER_NO_IO
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
The mixture: the parameters MixtureParams of weighted components of one type (decisions....
Moments: the mean and covariance that a Gaussian carries and that a mixture or a sample set is moment...
The particle set: ParticleSet, the weighted samples, with resampling, upkeep of weights and particles...
Printing: operator<< and std::formatter for every distribution, every filter (which prints its name a...
The resamplers: function objects that draw ancestors in proportion to weights, which ParticleSet::res...
SampleSet: a matrix whose columns are samples in a space, which the samplers return and the unscented...
Solving by a factorization of a small matrix: transposedSolve() gives , the shape of a Kalman gain an...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Weighted statistics over points, all in internal, which the containers of weighted elements (Gaussian...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
void reserve(Index n)
Reserves room for n components without setting a capacity.
Definition Mixture.h:1080
allocator_type get_allocator() const
Definition Mixture.h:2150
SpaceType const & space() const
Definition Mixture.h:2143
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
a matrix over differences, such as the covariance
Definition Moments.h:76
DifferenceVector< Space_, Scalar > DifferenceVectorType
Definition Moments.h:75
ValueVector< Space_, Scalar > VectorType
Definition Moments.h:74
A set of weighted samples: the empirical distribution.
Definition ParticleSet.h:279
allocator_type get_allocator() const
Definition ParticleSet.h:599
Space_ const & space() const
Definition ParticleSet.h:652
A type usable as the space of a distribution.
Definition Space.h:186
bool allFinite(Eigen::DenseBase< Derived > const &x)
Definition Math.h:354
bool isFinite(T const &x)
Definition Math.h:305
Scalar log2Pi()
Definition Math.h:177
Mixture< Gaussian< Space_, Scalar_ >, Allocator_ > GaussianMixture
The Mixture of a GaussianMixtureParams, the spelling for the common case (decisions....
Definition Mixture.h:2330
Eigen::Matrix< Scalar, valueDimensionAtCompileTime< Space >, Eigen::Dynamic,(maxValueDimensionAtCompileTime< Space >==1 ? Eigen::RowMajor :Eigen::ColMajor), maxValueDimensionAtCompileTime< Space >, Eigen::Dynamic > SampleSet
A matrix whose columns are samples in Space (see Spaces), with the space's size and storage bound.
Definition SampleSet.h:33
FitError
Reasons fitMixture() can fail.
Definition Fitting.h:41
MixtureParams< Gaussian< Space_, Scalar_ >, Allocator_ > GaussianMixtureParams
A Mixture of Gaussians in covariance form, the spelling for the common case (decisions....
Definition Mixture.h:2072
std::expected< MixtureFit< Space, Scalar, Allocator >, FitError > fitMixture(ParticleSet< Space, Scalar, Allocator > const &particles, Eigen::Index k, G &rng, FitOptions< Scalar > const &options={})
Definition Fitting.h:532
@ Degenerate
the source has no spread in some dimension, e.g. every particle has the same state
Definition Fitting.h:42
@ InvalidWeights
the particles' weights cannot be normalized (see ParticleSet::logTotalWeight())
Definition Fitting.h:43
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
Derived::Scalar logLikelihood(M const &measurement, Space const &space, Eigen::MatrixBase< Derived > const &x)
Definition Likelihood.h:433
auto minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
Options for fitMixture().
Definition Fitting.h:51
int maxIterations
Maximum number of EM iterations (E-steps).
Definition Fitting.h:53
Scalar regularization
Covariance floor, relative to the source's variance in each dimension: every component covariance get...
Definition Fitting.h:64
Scalar tolerance
Stop when an iteration improves the weighted log-likelihood by at most this.
Definition Fitting.h:56
Scalar minLogWeight
Components whose normalized log-weight falls below this are dropped.
Definition Fitting.h:67
Result of fitMixture(), in Space, with the source's allocator.
Definition Fitting.h:75
Scalar logLikelihood
Definition Fitting.h:77
GaussianMixtureParams< Space, Scalar, Allocator > mixture
Definition Fitting.h:76