[feat] SID: add sid_quality_report tool for offline collision/codebook stats#596
Conversation
…k stats Reads the `codes` column of a tzrec.predict SID table and reports, over the whole corpus: no_collision_rate, collision_free_item_rate, max_collision, gini/entropy/max_entropy/entropy_ratio, and a per-layer codebook table (coverage/dead_codes/perplexity). The exact global complement to the per-batch UniqueRatio metric. - framework create_reader/create_writer (csv/parquet/odps); reads only `codes` - counting via mixed-radix int64 SID ids + np.unique (no per-row Counter; ~9x faster at 5M rows), overflow-guarded - torch.special.entr for entropy (~10x vs numpy on CPU); numpy elsewhere - --codebook required + validated - scalar summary table + long-format per-layer table (csv/parquet/odps-safe) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fork state after merging alibaba/TorchEasyRec master (1.3.1): adds the sid_quality_report SID-eval tool. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dd tests - relocate tzrec/tools/sid_quality_report.py -> the tzrec/tools/sid/ subpackage - harden malformed / out-of-range input (from code review): build_arr filters null rows/elements, wrong-width and non-integer rows per row (no whole-batch drop); out-of-range codes are skipped rather than clamped (clamping fabricated collisions); dropped rows are counted and reported (malformed vs out-of-range) - crash-safe parse_codes: a non-integer string token no longer aborts the run - raise ValueError (not SystemExit) on empty input - add tzrec/tools/sid/sid_quality_report_test.py (unit + end-to-end, incl. a malformed/out-of-range regression case) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xample The docstring Example used `\\` (escaped backslash) instead of a single `\`, so copy-pasting it into a shell broke line continuation (each --flag ran as its own command). Use single backslashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…quely_identified_item_rate Clearer name for "fraction of items whose SID is theirs alone (count==1)", and less confusable with no_collision_rate (= unique_sid / total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review summarySolid, well-engineered tool. The mixed-radix int64 encoding → single terminal Noteworthy items posted inline: opaque Minor (non-blocking):
|
…ndling
Move the scan/finalize core of sid_quality_report into an importable run_report(),
leaving only argparse/reader/writer wiring in __main__, so the numeric branches
(mixed-radix encode/decode, capacity guard, per-layer stats) are unit-tested
in-process instead of only through the subprocess CLI. Harden the comma-string
codes path: a blank field ("1,2,,3") or an int64-overflowing token is dropped as
malformed rather than silently compacted into a fabricated SID or crashing
np.asarray(int64); a mistyped --codes_field fails with a clean usage error instead
of a KeyError mid-scan. Tests switch to an asymmetric codebook that actually pins
radix ordering and per-layer indexing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # tzrec/version.py
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Static review complete. The mixed-radix encoding and malformed/out-of-range row handling are thoughtfully structured. I left five focused inline findings covering input-type correctness, histogram/top-N scaling, Gini interpretation, and cross-batch coverage. Please also refresh the PR description before merge: it still points to No tests or builds were run, per the static-review request. |
…p_sids build_arr accepted any Arrow list schema, so a list<float> codes column crashed in np.bincount on the clean fast path yet was silently truncated to int64 on the malformed-row fallback -- the result depended on batch cleanliness. It now rejects non-integer element types up front and normalizes accepted ints to int64. A negative --log_top_sids was truthy and sliced [:-1], logging almost every SID; N is now validated as positive in both the CLI and run_report. Also documents that Gini measures inequality among occupied SID buckets, and adds a cross-batch aggregation test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| all_ids.append(arr @ radix) | ||
| layer_hist = update_layer_hist(layer_hist, arr, codebook) | ||
|
|
||
| if n_malformed or n_oob: |
There was a problem hiding this comment.
n_malformed and n_oob only reach the log, while the saved summary calls the surviving count total. A wrong codebook can therefore drop much of the corpus yet persist healthy-looking global rates. Could we include input/skipped counts in the summary, or fail on out-of-range input?
| ) | ||
| parser.add_argument( | ||
| "--batch_size", | ||
| type=int, |
There was a problem hiding this comment.
type=int accepts zero. Once the shared reader buffers data, a zero batch size repeatedly takes an empty slice without consuming that buffer, so --batch_size 0 can hang on nonempty input. Please reuse _positive_int here.
| log_top_sids=args.log_top_sids, | ||
| ) | ||
|
|
||
| if args.summary_output: |
There was a problem hiding this comment.
Please reject output targets that overlap the input or each other before creating these writers. CSV/Parquet writers overwrite part-0 and ODPS uses overwrite mode, so an output in the input directory/table can destroy the source; equal output paths also make layer stats replace the summary.
|
Static review complete. I left three inline findings covering auditable skipped-row accounting, validation of non-positive batch sizes, and protection against output targets overwriting source data or each other. Other specialist suggestions were either already present on the PR or not material enough to repeat. No tests or builds were run. |
What
Adds
tzrec/tools/sid_quality_report.py— a single-process CLI that measures the quality of generated Semantic IDs (SIDs) over a whole item corpus. It is the exact, global complement to the per-batchtzrec/metrics/unique_ratio.py::UniqueRatio(which is batch-size-biased and never sees the full corpus at once).It reads the
codescolumn of atzrec.predictoutput for a SID model and reports:no_collision_rate,collision_free_item_rate,max_collision,gini,entropy,max_entropy,entropy_ratio;coverage,dead_codes,perplexity.Design
create_reader/create_writer— CSV / Parquet / MaxCompute for both read and write; reads only thecodescolumn (no dependency on predict--reserved_columns).np.unique(no per-row PythonCounter) — ~9x faster at 5M rows; overflow-guarded (product of codebook sizes must fit int64).torch.special.entr(~10x vs numpy on CPU, multithreaded); numpy elsewhere.--codebookis required and validated; out-of-range codes are clamped with a one-shot warning.Testing
Differentially tested against a
Counterreference (200 random trials over L=1..4 codebooks + a 200k-row fixture): identical unique count / frequency distribution / decoded top-SIDs, ~9x faster counting. Edge cases verified: out-of-range clamping, int64 overflow guard, and the CSV string-codes path.ruff check+ruff formatclean.