Spaces:
Sleeping
Sleeping
Download app/training/build_master_dataset.py from Rthur2003/crowncode-backend: direct link, hf CLI and curl.
- Browser
- Download file 6.45 kB
-
https://huggingface.co/spaces/Rthur2003/crowncode-backend/resolve/main/app/training/build_master_dataset.py
- Command line
-
hf download hf://spaces/Rthur2003/crowncode-backend/app/training/build_master_dataset.py
-
curl -L -o build_master_dataset.py https://huggingface.co/spaces/Rthur2003/crowncode-backend/resolve/main/app/training/build_master_dataset.py
6.45 kB
| """ | |
| Joins the raw feature matrix with source/genre/generator metadata. | |
| The training pipeline (train_classifier.py) reads DataSet/features.csv, which | |
| contains only the 47 acoustic features plus file_path/label_int. It has no | |
| notion of which AI generator or genre a sample came from, so none of the | |
| reviewer-requested analyses (Leave-One-Generator-Out, dataset/genre bias, | |
| segment-leakage checks) can be run against it directly. | |
| DataSet/metadata.csv carries that provenance (source=generator, genre, | |
| ai_model) but was produced by a separate pass and only covers a subset of | |
| files. This script joins the two by filename and writes | |
| DataSet/features_with_meta.csv, reporting anything that fails to join so the | |
| gap is visible rather than silently dropped. | |
| Usage: | |
| python -m app.training.build_master_dataset | |
| """ | |
| from __future__ import annotations | |
| import csv | |
| import re | |
| import sys | |
| from pathlib import Path | |
| DATASET_DIR = Path("D:/CrownCode/DataSet") | |
| FEATURES_CSV = DATASET_DIR / "features.csv" | |
| METADATA_CSV = DATASET_DIR / "metadata.csv" | |
| OUTPUT_CSV = DATASET_DIR / "features_with_meta.csv" | |
| UNMATCHED_REPORT_CSV = DATASET_DIR / "features_with_meta_unmatched_report.csv" | |
| LABEL_MISMATCH_CSV = DATASET_DIR / "features_with_meta_label_mismatches.csv" | |
| # Files under DataSet/*/ai/echoes_<generator>_*.{wav,mp3} have no row in | |
| # metadata.csv, but the generator is recoverable from the filename prefix. | |
| _ECHOES_PREFIX = re.compile(r"^echoes_([a-zA-Z0-9]+)_", re.IGNORECASE) | |
| def _filename(path: str) -> str: | |
| return path.replace("\\", "/").rsplit("/", 1)[-1] | |
| def _infer_generator_from_filename(filename: str, label_int: int) -> tuple[str, str] | None: | |
| """Best-effort (generator, ai_model) recovery for files absent from metadata.csv.""" | |
| m = _ECHOES_PREFIX.match(filename) | |
| if m: | |
| return "echoes", f"echoes_{m.group(1).lower()}" | |
| if label_int == 0: | |
| return "archive_org", "" | |
| return None | |
| def build() -> None: | |
| with open(FEATURES_CSV, "r", encoding="utf-8") as f: | |
| reader = csv.DictReader(f) | |
| feature_fieldnames = reader.fieldnames or [] | |
| feature_rows = list(reader) | |
| with open(METADATA_CSV, "r", encoding="utf-8") as f: | |
| meta_rows = list(csv.DictReader(f)) | |
| meta_by_filename: dict[str, dict] = {} | |
| for row in meta_rows: | |
| meta_by_filename[_filename(row["path"])] = row | |
| matched_direct = 0 | |
| matched_inferred = 0 | |
| truly_unmatched: list[dict] = [] | |
| label_mismatches: list[dict] = [] | |
| out_fieldnames = feature_fieldnames + [ | |
| "genre", "generator", "ai_model", "meta_sample_rate", "meta_duration_sec", | |
| "match_source", | |
| ] | |
| with open(OUTPUT_CSV, "w", newline="", encoding="utf-8") as f_out: | |
| writer = csv.DictWriter(f_out, fieldnames=out_fieldnames) | |
| writer.writeheader() | |
| for row in feature_rows: | |
| fname = _filename(row["file_path"]) | |
| meta = meta_by_filename.get(fname) | |
| if meta is not None: | |
| meta_label_int = 1 if meta["label"] == "ai" else 0 | |
| if int(row["label_int"]) != meta_label_int: | |
| mismatch_row = dict(row) | |
| mismatch_row["features_label_int"] = row["label_int"] | |
| mismatch_row["metadata_label"] = meta["label"] | |
| mismatch_row["generator"] = meta["source"] | |
| mismatch_row["genre"] = meta["genre"] | |
| label_mismatches.append(mismatch_row) | |
| # features.csv (the file the training pipeline actually | |
| # reads) is treated as the ground-truth label; metadata's | |
| # label disagreement is recorded but does not override it. | |
| out_row = dict(row) | |
| out_row["genre"] = meta["genre"] | |
| out_row["generator"] = meta["source"] | |
| out_row["ai_model"] = meta["ai_model"] | |
| out_row["meta_sample_rate"] = meta["sample_rate"] | |
| out_row["meta_duration_sec"] = meta["duration_sec"] | |
| out_row["match_source"] = "metadata_csv" | |
| writer.writerow(out_row) | |
| matched_direct += 1 | |
| continue | |
| inferred = _infer_generator_from_filename(fname, int(row["label_int"])) | |
| if inferred is not None: | |
| generator, ai_model = inferred | |
| out_row = dict(row) | |
| out_row["genre"] = "" | |
| out_row["generator"] = generator | |
| out_row["ai_model"] = ai_model | |
| out_row["meta_sample_rate"] = "" | |
| out_row["meta_duration_sec"] = "" | |
| out_row["match_source"] = "filename_inferred" | |
| writer.writerow(out_row) | |
| matched_inferred += 1 | |
| continue | |
| truly_unmatched.append(row) | |
| if truly_unmatched: | |
| with open(UNMATCHED_REPORT_CSV, "w", newline="", encoding="utf-8") as f_out: | |
| writer = csv.DictWriter(f_out, fieldnames=feature_fieldnames) | |
| writer.writeheader() | |
| writer.writerows(truly_unmatched) | |
| if label_mismatches: | |
| mismatch_fieldnames = feature_fieldnames + [ | |
| "features_label_int", "metadata_label", "generator", "genre", | |
| ] | |
| with open(LABEL_MISMATCH_CSV, "w", newline="", encoding="utf-8") as f_out: | |
| writer = csv.DictWriter(f_out, fieldnames=mismatch_fieldnames) | |
| writer.writeheader() | |
| writer.writerows(label_mismatches) | |
| total_matched = matched_direct + matched_inferred | |
| print(f"features.csv rows: {len(feature_rows)}") | |
| print(f"metadata.csv rows: {len(meta_rows)}") | |
| print(f"matched via metadata.csv: {matched_direct}") | |
| print(f"matched via filename inference: {matched_inferred}") | |
| print(f"total matched: {total_matched} / {len(feature_rows)}") | |
| print(f"truly unmatched: {len(truly_unmatched)}") | |
| print(f"label mismatches (features.csv vs metadata.csv label): {len(label_mismatches)}") | |
| print(f"\nOutput: {OUTPUT_CSV}") | |
| if truly_unmatched: | |
| print(f"Unmatched report: {UNMATCHED_REPORT_CSV}") | |
| if label_mismatches: | |
| print(f"Label mismatches: {LABEL_MISMATCH_CSV}") | |
| gens = {} | |
| for m in label_mismatches: | |
| gens[m["generator"]] = gens.get(m["generator"], 0) + 1 | |
| print(f" by generator: {gens}") | |
| if __name__ == "__main__": | |
| build() | |