Initial text cleanup
This commit is contained in:
@@ -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 0–255 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`.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user