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 main says now. A link to a decision or a migration page follows main, 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.