Decomposition 0.2.0 factorization - CyrilB1531/lodestar GitHub Wiki
Lodestar.Decomposition 0.2.0. 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.
Factorization — Lodestar.Decomposition
Two factorizations of a sparse matrix, and the settings each takes. Both take a CsrMatrix
and neither centres it; what separates them is whether the components may be negative.
Truncated SVD
A term-document matrix has one column per word and one row per document, and almost every entry
is zero. This page factorizes it into a handful of dense components — latent semantic analysis —
without centring it first, which is the whole reason the operation exists: centring a sparse
matrix fills it in, and a corpus that fitted in memory as a CsrMatrix does not fit as a dense
one. That is also what separates this from PCA, which centres and is therefore a different
answer.
The factorization is randomized, not exact. A thin random block Ω probes the matrix's range, a few power iterations sharpen it, and the singular values fall out of a small dense problem whose size is the rank you asked for rather than the size of the corpus. The cost is that two runs from different Ω disagree in the last digits, which is why Ω is handled the way it is below.
Ω is an input, not a seed. TruncatedSvdOptions.RandomMatrix
takes the block itself, so a run of this package and a run of scikit-learn can be compared entry
by entry instead of hoping two generators agree; Seed is there for when you only need
repeatability and draws from this package's own generator, which is not NumPy's. See
ADR 0072.
| Type | What it is |
|---|---|
TruncatedSvd |
A fitted factorization: its components, its singular values, and the projection of new rows onto them. |
TruncatedSvdOptions |
Oversampling, power iterations, the normalizer, and Ω itself. |
PowerIterationNormalizer |
What happens to the probe block between the two products of a power iteration. |
The whole procedure is two calls: TruncatedSvd.Fit on the
corpus, then TruncatedSvd.Transform on whatever you
want projected — including the corpus itself.
Nothing above is a topic model. The components are signed and orthogonal, so a "topic" may subtract a word as readily as add one. Non-negative matrix factorization is the decomposition that answers that question, and it is the other half of this page.
Non-negative matrix factorization
The same corpus, factorized into two blocks neither of which holds a negative number: X ≈ W H,
where a row of H is a component over the terms and a row of W is one document's mixture of
those components. Nothing subtracts, so a component is a set of words that occur together and a
document is a recipe rather than a coordinate — which is what makes the output readable in a way
an SVD's is not, and what costs it the SVD's uniqueness. The answer is a local minimum, and the
initialisation decides which one.
| Type | What it is |
|---|---|
Nmf |
A fitted factorization: W, H, how many updates ran, and how far the product is from the matrix. |
NmfOptions |
The loss, the initialisation, the iteration cap, the tolerance, and Ω. |
NmfBetaLoss |
What the factorization minimises — a Gaussian noise model or a Poisson one. |
NmfInitialization |
Where the iteration starts — the two NNDSVD variants this package ships. |
The kernel underneath, now published
The randomized SVD needs an orthonormal basis for a projected block, and this package writes its
own QR rather than take Math.NET — 5.0.0 dates from April 2022 and nothing stable has followed
(decisions/0059).
That kernel is published now, because a second package needs it rather than a second copy of it.
The LU and the one-sided Jacobi SVD beside it stay internal: nothing has asked for them, and an
unpublished API can still be published later where the reverse is not true.
| Type | What it is |
|---|---|
QrDecomposition |
A thin QR by Householder reflections: the orthonormal factor and the triangular one. |
The whole procedure is one call, Nmf.Fit, in either of two forms:
hand it a rank and it computes the initialisation, or hand it W₀ and H₀ and it runs the
updates on exactly those.
The updates are multiplicative, so a zero is permanent. Every entry of
WandHis scaled by a non-negative ratio, which is what keeps both factors non-negative with no projection — and what means the sparsity of the answer was chosen by the initialisation, not found in the data.
See also — the Python equivalence table, which maps every member on these pages to the scikit-learn call it matches.