Decomposition 0.1.1 nmf fit - CyrilB1531/lodestar GitHub Wiki
Lodestar.Decomposition 0.1.1. This page is frozen at that release. Read the current documentation for what
mainsays now. A link to a decision or a migration page followsmain, and leaves the archive.
Nmf.Fit
Factorizes a sparse non-negative matrix into two non-negative factors, by Lee and Seung's multiplicative updates.
public static Nmf Fit(CsrMatrix matrix, int componentCount, NmfOptions options = null)
public static Nmf Fit(CsrMatrix matrix, double[] initialWeights, double[] initialComponents, NmfOptions options = null)
Parameters โ matrix is the matrix to factorize, rows as samples and columns as features; it
is read, never modified, and it must hold no negative value. componentCount is the rank to keep,
at least 1 and no greater than min(rows, columns), which is scikit-learn's own bound.
initialWeights is Wโ, row-major matrix.RowCount ร componentCount, and initialComponents is
Hโ, row-major componentCount ร matrix.ColumnCount; both are non-negative, both are copied, and
the rank is read off their lengths rather than passed again โ so that overload, and only that one,
reaches a rank above min(rows, columns). options carries the solver's settings, or is left out
for scikit-learn's defaults: the Frobenius loss, NNDSVD, 200 iterations and a tolerance of 1e-4.
Returns โ an Nmf holding W, H, the iteration count and the reconstruction error. Every
property is populated; there is no second call to make.
Exceptions โ ArgumentNullException when matrix, initialWeights or initialComponents is
null. ArgumentOutOfRangeException when componentCount is below 1 or above the smaller of the two
dimensions, or when MaxIterations is below one or Tolerance is negative.
ArgumentException when matrix holds a negative value or a NaN, on either overload โ the
precondition is checked rather than assumed, since a negative entry does not fail the loop, it
returns signed factors under the Frobenius loss and NaN under KullbackโLeibler. ArgumentException
too when the two blocks do not agree on a component count, do not fit the matrix, or hold a negative
number โ and when RandomMatrix is given and is not
matrix.ColumnCount ร (componentCount + 10) values long.
Example โ four documents over three terms, factorized at rank 2.
using Lodestar.Abstractions;
using Lodestar.Decomposition;
// Row-major CSR: values, the column each sits in, and where each row starts.
CsrMatrix matrix = new(
4, 3,
[3.0, 1.0, 2.0, 1.0, 1.0, 4.0, 2.0, 3.0],
[0, 1, 0, 2, 1, 2, 0, 2],
[0, 2, 4, 6, 8]);
Nmf fitted = Nmf.Fit(matrix, 2);
int rounds = fitted.Iterations; // => 50
double error = Math.Round(fitted.ReconstructionError, 3); // => 1.066
double firstTerm = Math.Round(fitted.Components[0], 3); // => 0.049
Example โ the same matrix from an initialisation written down rather than computed, with the early stop disabled so the iteration count is an input.
using Lodestar.Abstractions;
using Lodestar.Decomposition;
CsrMatrix corpus = new(
4, 3,
[3.0, 1.0, 2.0, 1.0, 1.0, 4.0, 2.0, 3.0],
[0, 1, 0, 2, 1, 2, 0, 2],
[0, 2, 4, 6, 8]);
// Wโ is 4 ร 2 and Hโ is 2 ร 3, both row-major and neither holding a negative number.
double[] initialWeights = [1.0, 0.5, 0.5, 1.0, 0.8, 0.8, 1.0, 0.2];
double[] initialComponents = [1.0, 0.5, 1.0, 0.5, 1.0, 1.0];
Nmf model = Nmf.Fit(
corpus, initialWeights, initialComponents,
new NmfOptions { MaxIterations = 100, Tolerance = 0.0 });
int ran = model.Iterations; // => 100
double reached = Math.Round(model.ReconstructionError, 3); // => 1.066
Remarks โ the two overloads are one algorithm. The first computes Wโ and Hโ with
NmfInitialization and then calls the second, so anything the
initialisation decides โ most of all which entries are zero, because a multiplicative update can
never revive one โ is decided before the loop starts.
Each iteration updates W first and H second, against the already-updated W. Updating both
against the old pair is a different algorithm that converges more slowly, and it is why an
implementation that looks right can be close and never equal.
The stopping rule is scikit-learn's: the relative improvement is measured every tenth iteration
and never on the others, against the divergence at the initialisation. A
Tolerance of zero disables it, which turns MaxIterations into an exact
iteration count โ the form the oracle corpus freezes, and the form to use when two runs must be
compared step for step.
The answer is a local minimum, not the minimum. Two initialisations reach two different
factorizations of the same rank, both non-negative and both valid; NndSvd is deterministic once
ฮฉ is fixed, which is what makes a run repeatable at all.
Applies to โ net10.0, netstandard2.0.
See also โ Nmf, NmfOptions, NmfBetaLoss,
NmfInitialization, the
Python equivalence table.