Spaces:
Paused
Paused
Download src/clean_fever.py from Param0304/truthlens-ml-api: direct link, hf CLI and curl.
- Browser
- Download file 8.03 kB
-
https://huggingface.co/spaces/Param0304/truthlens-ml-api/resolve/main/src/clean_fever.py
- Command line
-
hf download hf://spaces/Param0304/truthlens-ml-api/src/clean_fever.py
-
curl -L -o clean_fever.py https://huggingface.co/spaces/Param0304/truthlens-ml-api/resolve/main/src/clean_fever.py
8.03 kB
| """ | |
| clean_fever.py - Data Cleaning and Canonicalization for FEVER. | |
| Handles: | |
| 1. Identifying duplicate claims and claims with conflicting labels. | |
| 2. Quarantining conflicting claims (753 unique claims, 2,557 rows) into | |
| ml/data/processed/fever/fever_quarantined_conflicts.csv. | |
| 3. Consolidating duplicate claims with identical labels into one canonical record, | |
| merging evidence sets without losing evidence pointers. | |
| 4. Preserving original Wikipedia page titles and sentence IDs without fabricating text. | |
| 5. Creating stratified train/validation splits (90/10) for Model B verification. | |
| 6. Logging all removed and quarantined records. | |
| """ | |
| import os | |
| import sys | |
| import json | |
| from collections import defaultdict | |
| from typing import Dict, List, Any, Tuple | |
| import pandas as pd | |
| import numpy as np | |
| from sklearn.model_selection import train_test_split | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from fever_loader import load_fever_raw, parse_fever_evidence | |
| from preprocessing import clean_text | |
| def extract_fever_coordinates(evidence_field: Any) -> List[Tuple[str, int]]: | |
| """ | |
| Extracts unique (wikipedia_page, sentence_id) pairs from FEVER evidence. | |
| """ | |
| coords = [] | |
| if not isinstance(evidence_field, list): | |
| return coords | |
| for ev_set in evidence_field: | |
| if isinstance(ev_set, list): | |
| for ev_item in ev_set: | |
| if isinstance(ev_item, list) and len(ev_item) >= 4: | |
| _, _, page, sent_id = ev_item[:4] | |
| if page is not None and sent_id is not None: | |
| coords.append((str(page), int(sent_id))) | |
| # Deduplicate while preserving order | |
| seen = set() | |
| unique_coords = [] | |
| for c in coords: | |
| if c not in seen: | |
| seen.add(c) | |
| unique_coords.append(c) | |
| return unique_coords | |
| def clean_and_canonicalize_fever( | |
| raw_file: str, | |
| processed_dir: str, | |
| final_verification_dir: str | |
| ) -> Dict[str, Any]: | |
| """ | |
| Cleans raw FEVER, isolates conflicting annotations, canonicalizes duplicate | |
| claims, and produces stratified splits for Model B. | |
| """ | |
| os.makedirs(processed_dir, exist_ok=True) | |
| os.makedirs(final_verification_dir, exist_ok=True) | |
| print(f"Loading raw FEVER from: {raw_file}") | |
| df_raw, malformed_count = load_fever_raw(raw_file) | |
| total_raw_rows = len(df_raw) | |
| # 1. Clean claim text safely | |
| df_raw["claim_clean"] = df_raw["claim"].apply(clean_text) | |
| df_raw["claim_norm"] = df_raw["claim_clean"].str.strip().str.lower() | |
| # 2. Identify claims with conflicting labels | |
| label_group = df_raw.groupby("claim_norm")["label"].unique() | |
| conflicting_claims_set = set(label_group[label_group.apply(len) > 1].index) | |
| df_conflicts = df_raw[df_raw["claim_norm"].isin(conflicting_claims_set)].copy() | |
| df_clean_pool = df_raw[~df_raw["claim_norm"].isin(conflicting_claims_set)].copy() | |
| # 3. Quarantine conflicting claims | |
| quarantined_csv = os.path.join(processed_dir, "fever_quarantined_conflicts.csv") | |
| df_conflicts.to_csv(quarantined_csv, index=False) | |
| print(f"[FEVER] Quarantined {len(df_conflicts):,} rows across {len(conflicting_claims_set):,} conflicting claims -> {quarantined_csv}") | |
| # 4. Canonicalize duplicate claims with identical labels | |
| canonical_records = [] | |
| claim_groups = defaultdict(list) | |
| for idx, row in df_clean_pool.iterrows(): | |
| claim_groups[row["claim_norm"]].append(row) | |
| merged_rows_count = 0 | |
| for norm_text, row_list in claim_groups.items(): | |
| first_row = row_list[0] | |
| claim_text = first_row["claim_clean"] | |
| raw_label = first_row["label"] | |
| label = "NOT_ENOUGH_INFO" if raw_label == "NOT ENOUGH INFO" else raw_label | |
| all_ids = [r["id"] for r in row_list] | |
| canonical_id = f"fever_{all_ids[0]}" | |
| # Merge all evidence coordinates across annotations | |
| all_coords = [] | |
| for r in row_list: | |
| all_coords.extend(extract_fever_coordinates(r.get("evidence", []))) | |
| # Deduplicate coordinates | |
| unique_coords = [] | |
| seen = set() | |
| for c in all_coords: | |
| if c not in seen: | |
| seen.add(c) | |
| unique_coords.append(c) | |
| if len(row_list) > 1: | |
| merged_rows_count += (len(row_list) - 1) | |
| # Format evidence string | |
| # Since Wikipedia raw text is NOT in local storage, preserve exact pointers | |
| if label == "NOT_ENOUGH_INFO" or len(unique_coords) == 0: | |
| evidence_str = "" | |
| source_str = "" | |
| else: | |
| evidence_str = "Wikipedia: " + "; ".join(f"{page} (sentence {sid})" for page, sid in unique_coords) | |
| source_str = ", ".join(sorted(list(set(page for page, _ in unique_coords)))) | |
| metadata_dict = { | |
| "unresolved_evidence_text": True if unique_coords else False, | |
| "evidence_references": [{"page": p, "sentence_id": s} for p, s in unique_coords], | |
| "original_ids": all_ids, | |
| "annotation_count": len(row_list), | |
| "verifiable": first_row.get("verifiable", "VERIFIABLE"), | |
| } | |
| canonical_records.append({ | |
| "claim_id": canonical_id, | |
| "claim": claim_text, | |
| "evidence": evidence_str, | |
| "label": label, | |
| "dataset": "fever", | |
| "source": source_str, | |
| "metadata": json.dumps(metadata_dict), | |
| }) | |
| df_canonical = pd.DataFrame(canonical_records) | |
| canonical_csv = os.path.join(processed_dir, "fever_cleaned_canonical.csv") | |
| df_canonical.to_csv(canonical_csv, index=False) | |
| print(f"[FEVER] Consolidated {len(df_clean_pool):,} clean rows into {len(df_canonical):,} canonical claims (merged {merged_rows_count:,} duplicate rows)") | |
| # 5. Log removed / quarantined records | |
| removed_records = [] | |
| for c_text in conflicting_claims_set: | |
| sub = df_conflicts[df_conflicts["claim_clean"] == c_text] | |
| removed_records.append({ | |
| "claim": c_text, | |
| "reason": "quarantined_conflicting_labels", | |
| "count_rows": len(sub), | |
| "labels_observed": "|".join(sub["label"].unique()), | |
| }) | |
| removed_records.append({ | |
| "claim": "[DUPLICATE_ROWS_MERGED]", | |
| "reason": "duplicate_claims_identical_labels_consolidated", | |
| "count_rows": merged_rows_count, | |
| "labels_observed": "various", | |
| }) | |
| pd.DataFrame(removed_records).to_csv(os.path.join(processed_dir, "fever_removed_records.csv"), index=False) | |
| # 6. Stratified Train / Validation Split (90% / 10%) | |
| train_df, valid_df = train_test_split( | |
| df_canonical, | |
| test_size=0.10, | |
| random_state=42, | |
| stratify=df_canonical["label"] | |
| ) | |
| train_file = os.path.join(final_verification_dir, "fever_train.csv") | |
| valid_file = os.path.join(final_verification_dir, "fever_valid.csv") | |
| train_df.to_csv(train_file, index=False) | |
| valid_df.to_csv(valid_file, index=False) | |
| print(f"[FEVER] Created Model B splits: Train={len(train_df):,}, Valid={len(valid_df):,} -> {final_verification_dir}") | |
| return { | |
| "raw_total": total_raw_rows, | |
| "quarantined_rows": len(df_conflicts), | |
| "quarantined_unique_claims": len(conflicting_claims_set), | |
| "merged_duplicate_rows": merged_rows_count, | |
| "clean_canonical_claims": len(df_canonical), | |
| "train_rows": len(train_df), | |
| "valid_rows": len(valid_df), | |
| "label_distribution": df_canonical["label"].value_counts().to_dict(), | |
| } | |
| if __name__ == "__main__": | |
| base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| clean_and_canonicalize_fever( | |
| raw_file=os.path.join(base_dir, "data", "raw", "fever", "train.jsonl"), | |
| processed_dir=os.path.join(base_dir, "data", "processed", "fever"), | |
| final_verification_dir=os.path.join(base_dir, "data", "final", "verification"), | |
| ) | |