Spaces:
Running on Zero
Running on Zero
File size: 5,365 Bytes
4a6ccb0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | """
Does this Space read a PDF the way the corpus was read?
This is the test the whole design rests on. Part A's index and Part B's F1 of 0.945 were measured
on `skeleton_masked` and `payload_window` strings produced by the EDA notebook's extractor. If
`corpus_text.py` produces even slightly different text, those numbers stop describing this app and
become decoration.
So it pulls real PDFs out of the generation repo, runs them through `corpus_text.py`, and compares
the result **character by character** against the published parquet. Not "close enough" — identical.
The same argument applies to the family-naming model, for the same reason and with less margin: it
was fitted on seventeen numeric columns of that parquet, and `doc_features.py` recomputes them from
an uploaded file. A feature that drifts does not throw - it shifts the model's input distribution
and the model keeps answering confidently. So the numeric block is checked against the published
columns too, and the assembled feature row is checked for width against the fitted estimator.
python test_fidelity.py [n_files]
"""
import sys
import numpy as np
import pandas as pd
import corpus_text
import doc_features
CORPUS = ("https://huggingface.co/datasets/Cyber-security-final-project/"
"HARMLESS_Synthetic_Injected_PDFs_EDA/resolve/main/Datasets/"
"synthetic_corpus_part2_clustered.parquet")
GENERATION_REPO = "Cyber-security-final-project/Generated_Injected_PDFs_HARMLESS"
def main(n=8):
from huggingface_hub import hf_hub_download
corpus = pd.read_parquet(CORPUS).set_index("file_id")
# Injected and clean both, and not all from one family: masking and binary-stream handling
# differ between them, and a test that only saw one would pass on a broken extractor.
ids = list(corpus.index)
sample = ids[::max(1, len(ids) // n)][:n]
failures = 0
for fid in sample:
row = corpus.loc[fid]
try:
path = hf_hub_download(GENERATION_REPO, f"Output_PDFs/{fid}", repo_type="dataset")
except Exception as e:
print(f" ? {fid}: could not fetch ({type(e).__name__})")
continue
data = open(path, "rb").read()
skeleton, truncated, dropped = corpus_text.build_skeleton(data)
masked = corpus_text.mask_leaks(skeleton)
window = corpus_text.payload_window(skeleton)
checks = {"skeleton_masked": (masked, row["skeleton_masked"]),
"payload_window": (window, row["payload_window"])}
# The single-window path must also be what the triage returns first for a marked file,
# or the model reads a different string here than the corpus was scored on.
top = corpus_text.candidate_windows(skeleton, cover_all=False)[0]
bad = [k for k, (got, want) in checks.items() if got != want]
# The family model's document-shape block, against the columns it was fitted on. Compared
# with a relative tolerance rather than exactly: the entropies and the compression ratio
# are floats that went through a parquet round-trip, and demanding bit-equality of those
# would fail on storage rather than on a real drift.
stats = doc_features.describe(path, data, skeleton, truncated, dropped)
drifted = [k for k in doc_features.NUMERIC
if k in row.index
and not np.isclose(float(stats[k]), float(row[k]), rtol=1e-4, atol=1e-6)]
if drifted:
bad += [f"feature {k}" for k in drifted]
for k in drifted:
print(f" {k}: computed {stats[k]}, corpus has {row[k]}")
if bad:
failures += 1
print(f" x {fid}: differs in {', '.join(bad)}")
for k in [b for b in bad if not b.startswith("feature ")]:
got, want = checks[k]
print(f" {k}: got {len(got):,} chars, corpus has {len(want):,}")
else:
note = "head" if top["is_head"] else f"{len(top['families'])} family marker(s)"
print(f" . {fid}: identical (triage top = {note}, "
f"{len(doc_features.NUMERIC)} features match)")
print(f"\n{len(sample) - failures}/{len(sample)} identical to the published corpus")
# Width of the assembled row against the fitted estimator. Cheap, and it is the check that
# catches a retrain that added a feature block without anyone updating `build_features`.
try:
import family_model
if family_model.AVAILABLE:
row = family_model.build_features(
skeleton, {"pred_family": "none", "pred_injected": 0, "parse_ok": 1},
stats, np.zeros(len(family_model.CLASSES), dtype=np.float32))
expected = family_model.load_model().n_features_in_
ok = row.shape[0] == expected
print(f"{'.' if ok else 'x'} feature row is {row.shape[0]} wide, "
f"the fitted model expects {expected}")
failures += 0 if ok else 1
except Exception as e:
print(f"? family model not checked: {type(e).__name__}: {e}")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main(int(sys.argv[1]) if len(sys.argv) > 1 else 8))
|