File size: 8,679 Bytes
81e7cac | 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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | """Dataset Integrity Auditor - Gradio front end for audit.py.
Paste any Hugging Face dataset id and get the duplication, degeneracy and
concentration report. Field roles are guessed when not supplied.
"""
import json
import traceback
import gradio as gr
import audit
# Auditing is O(n^2) below the LSH threshold and every run is synchronous, so
# large datasets are truncated rather than left to time the Space out.
MAX_RECORDS = 6000
TEXT_CANDIDATES = ["question", "prompt", "query", "instruction", "text",
"problem", "input", "issue"]
ANSWER_CANDIDATES = ["answer", "canonical_solution", "solution", "sql",
"query", "output", "issue_sql", "response", "code"]
EXAMPLES = [
["xlangai/spider", "validation", "", "question", "query"],
["openai/gsm8k", "test", "main", "question", "answer"],
["openai/openai_humaneval", "test", "", "prompt", "canonical_solution"],
["birdsql/bird-critic-1.0-open", "open", "", "query", "issue_sql"],
]
def guess_field(columns, candidates, exclude=None):
for c in candidates:
if c in columns and c != exclude:
return c
return None
def load_rows(dataset_id, split, config):
from datasets import load_dataset, get_dataset_split_names
kwargs = {"split": split}
if config.strip():
kwargs["name"] = config.strip()
try:
return load_dataset(dataset_id, **kwargs)
except ValueError as exc:
# The most common failure by far is a wrong split name - BIRD-CRITIC
# calls its only split "open", not "train". Say so instead of raising.
try:
available = get_dataset_split_names(
dataset_id, config.strip() or None)
raise gr.Error(
f"Split '{split}' not found. Available splits: "
f"{', '.join(available)}") from exc
except gr.Error:
raise
except Exception:
raise gr.Error(str(exc)) from exc
def run(dataset_id, split, config, text_field, answer_field,
progress=gr.Progress()):
dataset_id = (dataset_id or "").strip()
if not dataset_id:
raise gr.Error("Enter a dataset id, for example xlangai/spider")
progress(0.1, desc="Downloading")
ds = load_rows(dataset_id, (split or "train").strip(), config or "")
columns = list(ds.column_names)
text_field = (text_field or "").strip() or guess_field(
columns, TEXT_CANDIDATES)
if not text_field:
raise gr.Error(
f"Could not guess the text field. Columns are: "
f"{', '.join(columns)}. Name one in the Text field box.")
answer_field = (answer_field or "").strip() or guess_field(
columns, ANSWER_CANDIDATES, exclude=text_field)
total = len(ds)
truncated = total > MAX_RECORDS
if truncated:
ds = ds.select(range(MAX_RECORDS))
progress(0.4, desc="Auditing")
records = [dict(r) for r in ds]
roles = {"text": text_field, "answer": answer_field,
"group": guess_field(columns, ["db_id", "source", "category",
"subject", "domain"]),
"labels": []}
schema = audit.infer_schema(records)
schema_issues = audit.validate(records, schema)
degenerate = audit.find_degenerate(records, roles)
dup_text = audit.find_exact_dupes(
records, lambda r: audit.normalize_text(
audit.as_text(r.get(roles["text"]))))
sigs = [audit.shingles(audit.as_text(r.get(roles["text"])))
for r in records]
n = len(records)
if n > audit.LSH_SWITCH_AT:
method = "MinHash-LSH"
pairs, candidates = audit.lsh_pairs(
sigs, audit.NEAR_DUP_THRESHOLD, audit.make_perms(audit.MINHASH_PERMS))
else:
method = "exact all-pairs"
pairs = audit.all_pairs(sigs, audit.NEAR_DUP_THRESHOLD)
candidates = n * (n - 1) // 2
clusters = audit.connected_components(pairs, n)
identical, variant = audit.classify_clusters(clusters, records, roles)
dist = audit.distributions(records, roles)
progress(0.9, desc="Writing report")
n_identical = sum(c["size"] for c in identical)
pct = lambda x: f"{100.0 * x / n:.2f}%" if n else "n/a"
lines = [
f"## {dataset_id} [{split}]", "",
f"**{n} records audited**"
+ (f" (truncated from {total}; this Space caps at {MAX_RECORDS})"
if truncated else ""),
f"Fields: text=`{text_field}`"
+ (f", answer=`{answer_field}`" if answer_field else
", answer=*(none found - duplicate detection is text-only)*"),
"",
"| Check | Result |", "|---|---|",
f"| Schema issues | {len(schema_issues)} |",
f"| Degenerate records | {len(degenerate)} |",
f"| Exact duplicate text groups | {len(dup_text)} |",
f"| Near-duplicate clusters | {len(clusters)} |",
f"| — genuinely identical | **{len(identical)}** "
f"({n_identical} records, {pct(n_identical)}) |",
f"| — shared-stem variants | {len(variant)} |",
]
if dist.get("distinct_groups"):
lines.append(
f"| Distinct `{dist['group_field']}` values | "
f"{dist['distinct_groups']} "
f"(largest {100 * dist['top_group_share']:.1f}%) |")
lines += ["", f"Search: {method} over {candidates} candidate pairs.", ""]
if identical:
lines += ["### Genuinely duplicated", ""]
for c in identical[:10]:
lines.append(f"- {', '.join(c['members'][:6])}")
lines.append("")
if variant:
lines += [
"### Shared-stem variants (usually deliberate)", "",
f"{len(variant)} clusters share text but differ in the reference "
"answer or grouping. A naive dedup pass would remove these too - "
"which is the whole point of separating them.", "",
]
for c in variant[:5]:
lines.append(f"- {', '.join(c['members'][:6])}")
lines.append("")
if not identical and not degenerate and not schema_issues:
lines += ["---", "",
"**Nothing structurally wrong found.** Reported as-is."]
return "\n".join(lines), json.dumps(
{"dataset": dataset_id, "split": split, "records": n,
"schema_issues": len(schema_issues),
"degenerate": len(degenerate),
"identical_clusters": len(identical),
"identical_records": n_identical,
"variant_clusters": len(variant),
"method": method}, indent=2)
def safe_run(*args, progress=gr.Progress()):
try:
return run(*args, progress=progress)
except gr.Error:
raise
except Exception as exc:
traceback.print_exc()
raise gr.Error(f"{type(exc).__name__}: {exc}") from exc
with gr.Blocks(title="Dataset Integrity Auditor") as demo:
gr.Markdown(
"# Dataset Integrity Auditor\n"
"How much of a benchmark is actually redundant? Paste a Hugging Face "
"dataset id.\n\n"
"The point of this tool is one distinction: records that are "
"**identical across every evaluated field** versus records that merely "
"**share a question stem**. Naive similarity lumps them together and "
"overstates duplication - by 9x on Spider's training split. Only the "
"first kind lets a model bank the same answer twice.\n\n"
"Code and full write-up: "
"[github.com/ashishsinha1602/dataset-integrity-audit]"
"(https://github.com/ashishsinha1602/dataset-integrity-audit)")
with gr.Row():
dataset_id = gr.Textbox(label="Dataset id", value="xlangai/spider",
scale=3)
split = gr.Textbox(label="Split", value="validation", scale=1)
config = gr.Textbox(label="Config (optional)", value="", scale=1)
with gr.Row():
text_field = gr.Textbox(label="Text field (blank = guess)", value="")
answer_field = gr.Textbox(label="Answer field (blank = guess)", value="")
go = gr.Button("Audit", variant="primary")
report = gr.Markdown()
raw = gr.Code(label="summary.json", language="json")
gr.Examples(examples=EXAMPLES,
inputs=[dataset_id, split, config, text_field, answer_field])
go.click(safe_run,
inputs=[dataset_id, split, config, text_field, answer_field],
outputs=[report, raw])
if __name__ == "__main__":
demo.launch()
|