Initial text cleanup

This commit is contained in:
2026-08-19 12:55:19 -04:00
parent 819a4cce7a
commit cc84d70d52
26 changed files with 454 additions and 457 deletions

View File

@@ -1,7 +1,7 @@
# Stage-5: unknown-format discovery by Bayesian header clustering.
#
# See model/DESIGN_clustering.md for the full rationale. In brief: files that
# stage-3 sorted into `binary/` are the `:unknown` sink genuinely
# stage-3 sorted into `binary/` are the `:unknown` sink: genuinely
# unrecognized bytes. This stage clusters them by *file format* (not producer)
# using the first `HEADER_N` header bytes, modeled as a Dirichlet-process
# mixture of per-position categoricals over a 257-symbol alphabet
@@ -11,7 +11,7 @@
# This file is deliberately dependency-light: everything below is base Julia
# (only `log`, no `SpecialFunctions`), so it drops into the existing module and
# the offline calibration script alike without touching the Manifest. The model
# is categorical on purpose — do NOT reuse model.jl's [0,1] byte scaling here
# is categorical on purpose. Do NOT reuse model.jl's [0,1] byte scaling here
# (that metric is meaningful for the Lux net and meaningless for header bytes,
# where 0x89 and 0x88 are not "close"; see DESIGN §4).
@@ -22,7 +22,7 @@ const HEADER_N = 32
const ALPHABET = 257
"The 'past EOF' symbol (1-based index `ALPHABET`). A file shorter than a given
position emits this here real, discriminative signal for fixed-length formats,
position emits this here: real, discriminative signal for fixed-length formats,
and it avoids colliding zero-padding with genuine 0x00 header bytes (DESIGN §4)."
const PAST_EOF = ALPHABET
@@ -75,7 +75,7 @@ Sufficient statistics for one cluster: a per-position count table `counts`
(`n × ALPHABET`; `counts[i, v]` = how many member files show symbol `v` at
position `i`) and the member count `members`. These are exactly what phase-B
persists per catalog entry, and everything the collapsed predictive needs.
A slot with `members == 0` is inactive (reusable) the Gibbs sweep prunes
A slot with `members == 0` is inactive (reusable): the Gibbs sweep prunes
emptied clusters without renumbering, so surviving cluster ids stay stable.
"""
mutable struct ClusterStats
@@ -111,7 +111,7 @@ Dirichlet-Categorical predictive, given `c`'s current counts: at each position
`i`, `p(x_i | c) = (counts[i, x_i] + β) / (members + ALPHABET·β)`, summed in log
space over positions. Call with `c` NOT containing `x` (Gibbs excludes the point
being resampled), so an emptied cluster reduces to the uniform prior `1/ALPHABET`
per position identical to a brand-new cluster, as it should be.
per position, identical to a brand-new cluster, as it should be.
"""
function log_predictive(c::ClusterStats, x::AbstractVector{<:Integer}, β::Float64)
denom = log(c.members + ALPHABET * β)
@@ -129,7 +129,7 @@ log_uniform(n::Integer) = -n * log(ALPHABET)
# Lanczos approximation to log Γ(x) for x > 0, so partition scoring (below) needs
# the Dirichlet-multinomial marginal's gamma terms without pulling in
# SpecialFunctions keeping this stage dependency-flat (no Manifest churn).
# SpecialFunctions, keeping this stage dependency-flat (no Manifest churn).
# g = 7, standard coefficients; accurate to ~1e-14 over the range we use.
const _LANCZOS_G = 7
const _LANCZOS_C = (0.99999999999980993, 676.5203681218851, -1259.1392167224028,
@@ -148,7 +148,7 @@ function loggamma(x::Float64)
end
# ---------------------------------------------------------------------------
# Phase A: collapsed Gibbs sampler (offline the science)
# Phase A: collapsed Gibbs sampler (offline; the science)
# ---------------------------------------------------------------------------
"""
@@ -207,7 +207,7 @@ function _gibbs_once(X::AbstractMatrix{<:Integer};
# from the background deadlocks: at a format's first file, a fresh cluster
# and the background are equally uniform, so with bg_mass ≥ α the background
# always wins and no real cluster is ever seeded. Singleton init sidesteps
# this same-format singletons merge and snowball, while a lone
# this: same-format singletons merge and snowball, while a lone
# random-blob singleton dissolves on resample and is reclaimed by the
# (stickier) background. See DESIGN §4a.
z = collect(1:N)
@@ -274,7 +274,7 @@ function _gibbs_once(X::AbstractMatrix{<:Integer};
return GibbsResult(z, clusters, partition_logmarginal(X, z, clusters, α, β))
end
"Argmax of `logw .+ Gumbel noise` an exact draw from softmax(logw) without
"Argmax of `logw .+ Gumbel noise`: an exact draw from softmax(logw) without
normalizing (numerically safe for the tiny header-likelihood magnitudes)."
function _gumbel_argmax(logw::AbstractVector{Float64}, rng::AbstractRNG)
best_i = 1
@@ -293,12 +293,12 @@ end
partition_logmarginal(X, z, clusters, α, β) -> Float64
The joint log-evidence `log p(z, X)` of a partition under the CRP prior and the
collapsed Dirichlet-Categorical likelihood the principled score for ranking
collapsed Dirichlet-Categorical likelihood. This is the principled score for ranking
Gibbs restarts (higher = better). It is the sum of:
* the Dirichlet-multinomial **marginal** of each cluster's per-position counts,
`lΓ(Aβ) lΓ(mₖ+Aβ) + Σ_v [lΓ(c_v+β) lΓ(β)]`, whose normalizer supplies the
Occam penalty that a plain product-of-predictives lacks — it is what makes a
Occam penalty that a plain product-of-predictives lacks. It is what makes a
*merged, heterogeneous* cluster score **worse** than two clean ones (an
earlier pseudo-likelihood scorer omitted this and wrongly rewarded merging);
* the CRP prior over the clustered points, `K·log α + Σₖ lΓ(mₖ) + lΓ(α)
@@ -335,7 +335,7 @@ function partition_logmarginal(X::AbstractMatrix{<:Integer}, z::AbstractVector{<
end
# ---------------------------------------------------------------------------
# Phase B: sequential CRP-predictive assignment (online the catalog)
# Phase B: sequential CRP-predictive assignment (online; the catalog)
# ---------------------------------------------------------------------------
"""
@@ -380,7 +380,7 @@ function assign_file(x::AbstractVector{<:Integer}, clusters::Dict{Int,ClusterSta
end
# ---------------------------------------------------------------------------
# Signatures and promotion (closing the loop to the classifier DESIGN §6)
# Signatures and promotion (closing the loop to the classifier; DESIGN §6)
# ---------------------------------------------------------------------------
"""
@@ -390,13 +390,13 @@ Turn a cluster's counts into a libmagic-style template: at each position, if the
modal symbol's posterior probability exceeds `peak_threshold`, that byte is
*required* (returned as the 0255 byte value, or `PAST_EOF`); otherwise the
position is a wildcard (`nothing`). The vector of required bytes IS the
magic-number template the whole point of the categorical model (DESIGN §4).
magic-number template, the whole point of the categorical model (DESIGN §4).
Peakedness is a **Bernoulli** question ("is this position fixed to byte `v`, or
not?"), so it uses a 2-way posterior mean `(count + β)/(members + 2β)` NOT the
not?"), so it uses a 2-way posterior mean `(count + β)/(members + 2β)`, NOT the
257-way mixture predictive. The alphabet-wide version would smear the estimate
across 257 symbols (`members + 257β` in the denominator), pulling even a
unanimous position below any sane threshold once β is small which would make
unanimous position below any sane threshold once β is small, which would make
promotion impossible. This decouples signature detection from the clustering
pseudocount and the alphabet size.
"""
@@ -415,7 +415,7 @@ function signature(c::ClusterStats; peak_threshold::Float64=0.9, β::Float64=0.5
return sig
end
"Number of fixed (non-wildcard) positions in a signature its 'magic length'."
"Number of fixed (non-wildcard) positions in a signature: its 'magic length'."
magic_positions(sig::AbstractVector) = count(!isnothing, sig)
"""
@@ -423,7 +423,7 @@ magic_positions(sig::AbstractVector) = count(!isnothing, sig)
A cluster qualifies for *nomination* (still human-gated, DESIGN §6) when it has
at least `min_members` files AND at least `min_magic` fixed signature positions.
The background (id 0) is never passed here it is never promotable by design.
The background (id 0) is never passed here; it is never promotable by design.
"""
function is_promotable(c::ClusterStats, sig::AbstractVector;
min_members::Integer=20, min_magic::Integer=3)
@@ -448,7 +448,7 @@ end
Adjusted Rand Index between two labelings of the same items: 1.0 = identical
partitions (up to relabeling), ~0.0 = chance agreement, can go negative. This is
the §7 calibration objective grid-tuning maximizes ARI of recovered-vs-truth
the §7 calibration objective: grid-tuning maximizes ARI of recovered-vs-truth
(magic-collapsed) labels. Hand-rolled to keep the dependency footprint flat;
matches `Clustering.randindex`.
"""