Spaces:
Sleeping
Sleeping
Download src/data_preprocessing.py from werty3684/misinformation-detector: direct link, hf CLI and curl.
- Browser
- Download file 25.2 kB
-
https://huggingface.co/spaces/werty3684/misinformation-detector/resolve/main/src/data_preprocessing.py
- Command line
-
hf download hf://spaces/werty3684/misinformation-detector/src/data_preprocessing.py
-
curl -L -o data_preprocessing.py https://huggingface.co/spaces/werty3684/misinformation-detector/resolve/main/src/data_preprocessing.py
25.2 kB
| """ | |
| Data acquisition, cleaning, and preprocessing module. | |
| Uses Hugging Face: load_dataset("kasperdinh/fake-news-detection") plus data in data/raw/. | |
| Handles HTML parsing, deduplication, and train/val/test splitting. | |
| """ | |
| import hashlib | |
| import re | |
| import warnings | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Tuple | |
| import nltk | |
| import numpy as np | |
| import pandas as pd | |
| from nltk.corpus import stopwords | |
| from nltk.stem import WordNetLemmatizer | |
| from nltk.tokenize import word_tokenize | |
| from sklearn.model_selection import train_test_split | |
| from src.config import ( | |
| PROCESSED_DATA_DIR, | |
| PROJECT_ROOT, | |
| RANDOM_SEED, | |
| RAW_DATA_DIR, | |
| TEST_SIZE, | |
| VAL_SIZE, | |
| ) | |
| warnings.filterwarnings("ignore") | |
| # Files to look for in project root and copy to data/raw | |
| RAW_DATA_FILES = ["FakeNewsNet.csv", "train.tsv", "valid.tsv", "test.tsv"] | |
| def organise_data_folders() -> None: | |
| """ | |
| Copy raw data from project root into data/raw so the loader finds them. | |
| Idempotent: only copies if source exists and target is missing or older. | |
| """ | |
| RAW_DATA_DIR.mkdir(parents=True, exist_ok=True) | |
| for name in RAW_DATA_FILES: | |
| src = PROJECT_ROOT / name | |
| dst = RAW_DATA_DIR / name | |
| if src.exists() and src.is_file(): | |
| if not dst.exists() or src.stat().st_mtime > dst.stat().st_mtime: | |
| import shutil | |
| shutil.copy2(src, dst) | |
| print(f"Organised: {name} -> data/raw/") | |
| class TextPreprocessor: | |
| """Text preprocessing pipeline for news articles.""" | |
| def __init__(self, download_nltk: bool = True): | |
| """ | |
| Initialize the preprocessor. | |
| Args: | |
| download_nltk: Whether to download required NLTK data | |
| """ | |
| if download_nltk: | |
| self._download_nltk_data() | |
| self.lemmatizer = WordNetLemmatizer() | |
| # Be resilient when running offline / behind restricted networks: | |
| # NLTK downloads can fail, and missing corpora would otherwise crash init. | |
| self.stop_words = set() | |
| try: | |
| self.stop_words = set(stopwords.words("english")) | |
| except LookupError: | |
| try: | |
| nltk.download("stopwords", quiet=True) | |
| self.stop_words = set(stopwords.words("english")) | |
| except Exception: | |
| self.stop_words = set() | |
| def _download_nltk_data(self): | |
| """Download required NLTK data packages.""" | |
| packages = ["punkt", "stopwords", "wordnet", "punkt_tab"] | |
| for package in packages: | |
| try: | |
| nltk.download(package, quiet=True) | |
| except Exception: | |
| pass | |
| def clean_html(self, text: str) -> str: | |
| """Remove HTML tags from text.""" | |
| if pd.isna(text): | |
| return "" | |
| clean = re.sub(r"<[^>]+>", "", str(text)) | |
| return clean | |
| def remove_urls(self, text: str) -> str: | |
| """Remove URLs from text.""" | |
| url_pattern = ( | |
| r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" | |
| ) | |
| return re.sub(url_pattern, "", text) | |
| def remove_special_chars(self, text: str) -> str: | |
| """Remove special characters, keeping only alphanumeric and spaces.""" | |
| return re.sub(r"[^a-zA-Z0-9\s]", "", text) | |
| def normalize_whitespace(self, text: str) -> str: | |
| """Normalize whitespace in text.""" | |
| return " ".join(text.split()) | |
| def tokenize(self, text: str) -> List[str]: | |
| """Tokenize text into words.""" | |
| try: | |
| return word_tokenize(text.lower()) | |
| except Exception: | |
| return text.lower().split() | |
| def remove_stopwords(self, tokens: List[str]) -> List[str]: | |
| """Remove stopwords from token list.""" | |
| return [t for t in tokens if t not in self.stop_words] | |
| def lemmatize(self, tokens: List[str]) -> List[str]: | |
| """Lemmatize tokens.""" | |
| try: | |
| return [self.lemmatizer.lemmatize(t) for t in tokens] | |
| except LookupError: | |
| # WordNet resource may be missing; try to fetch it and retry once. | |
| try: | |
| nltk.download("wordnet", quiet=True) | |
| return [self.lemmatizer.lemmatize(t) for t in tokens] | |
| except Exception: | |
| # Safe fallback: skip lemmatization if resources unavailable. | |
| return tokens | |
| def preprocess(self, text: str, remove_stops: bool = False, lemmatize: bool = False) -> str: | |
| """ | |
| Full preprocessing pipeline. | |
| Args: | |
| text: Input text | |
| remove_stops: Whether to remove stopwords | |
| lemmatize: Whether to lemmatize tokens | |
| Returns: | |
| Preprocessed text string | |
| """ | |
| if pd.isna(text) or text is None: | |
| return "" | |
| text = str(text) | |
| text = self.clean_html(text) | |
| text = self.remove_urls(text) | |
| text = self.normalize_whitespace(text) | |
| if remove_stops or lemmatize: | |
| tokens = self.tokenize(text) | |
| if remove_stops: | |
| tokens = self.remove_stopwords(tokens) | |
| if lemmatize: | |
| tokens = self.lemmatize(tokens) | |
| text = " ".join(tokens) | |
| return text | |
| def preprocess_for_bert(self, text: str) -> str: | |
| """ | |
| Minimal preprocessing for BERT (preserve more structure). | |
| Args: | |
| text: Input text | |
| Returns: | |
| Lightly preprocessed text | |
| """ | |
| if pd.isna(text) or text is None: | |
| return "" | |
| text = str(text) | |
| text = self.clean_html(text) | |
| text = self.remove_urls(text) | |
| text = self.normalize_whitespace(text) | |
| return text | |
| class DatasetLoader: | |
| """Load and prepare datasets for misinformation detection.""" | |
| def __init__(self): | |
| self.preprocessor = TextPreprocessor() | |
| def create_synthetic_dataset(self, n_samples: int = 1000) -> pd.DataFrame: | |
| """ | |
| Create a synthetic dataset for testing/demonstration. | |
| Args: | |
| n_samples: Number of samples to generate | |
| Returns: | |
| DataFrame with synthetic news data | |
| """ | |
| np.random.seed(RANDOM_SEED) | |
| # Templates for credible news | |
| credible_templates = [ | |
| "According to official sources, {topic} has been confirmed by multiple experts.", | |
| "A new study published in {journal} reveals findings about {topic}.", | |
| "Government officials announced today that {topic} will be implemented.", | |
| "Researchers at {university} have discovered new evidence regarding {topic}.", | |
| "The report, verified by independent analysts, shows that {topic}.", | |
| "Multiple news agencies have confirmed that {topic} occurred yesterday.", | |
| "Scientists from {institution} presented their findings on {topic} at the conference.", | |
| "The official statement clarifies the situation regarding {topic}.", | |
| "Data from reputable sources indicates that {topic} is accurate.", | |
| "Experts interviewed by major outlets agree that {topic} is significant.", | |
| ] | |
| # Templates for misinformation | |
| misinfo_templates = [ | |
| "BREAKING: You won't believe what they're hiding about {topic}!!!", | |
| "SHOCKING REVELATION: The truth about {topic} they don't want you to know!", | |
| "EXPOSED: Secret plot involving {topic} finally revealed!", | |
| "URGENT: Share before they delete! The real story about {topic}!", | |
| "CONSPIRACY CONFIRMED: {topic} is part of a massive cover-up!", | |
| "Wake up people! {topic} is just the beginning of the deception!", | |
| "They're lying to you about {topic}! Here's the proof!", | |
| "BOMBSHELL: Insider reveals the truth about {topic}!", | |
| "MUST READ: The mainstream media won't tell you this about {topic}!", | |
| "ALERT: {topic} is being censored! Share now!", | |
| ] | |
| topics = [ | |
| "the new policy", | |
| "vaccine effectiveness", | |
| "election results", | |
| "climate change data", | |
| "economic forecasts", | |
| "health guidelines", | |
| "scientific research", | |
| "government spending", | |
| "international relations", | |
| "technology developments", | |
| "environmental regulations", | |
| "public safety measures", | |
| ] | |
| journals = ["Nature", "Science", "The Lancet", "JAMA", "NEJM"] | |
| universities = ["Harvard", "MIT", "Stanford", "Oxford", "Cambridge"] | |
| institutions = ["NIH", "CDC", "WHO", "FDA", "EPA"] | |
| data = [] | |
| for i in range(n_samples): | |
| is_fake = np.random.random() < 0.3 # 30% misinformation | |
| topic = np.random.choice(topics) | |
| if is_fake: | |
| template = np.random.choice(misinfo_templates) | |
| title = template.format(topic=topic) | |
| body = f"{title} " * np.random.randint(3, 8) | |
| else: | |
| template = np.random.choice(credible_templates) | |
| title = template.format( | |
| topic=topic, | |
| journal=np.random.choice(journals), | |
| university=np.random.choice(universities), | |
| institution=np.random.choice(institutions), | |
| ) | |
| body = f"{title} Additional details support this conclusion. " * np.random.randint( | |
| 2, 5 | |
| ) | |
| # Generate fake dates for temporal splitting | |
| year = np.random.choice([2022, 2023, 2024], p=[0.4, 0.4, 0.2]) | |
| month = np.random.randint(1, 13) | |
| day = np.random.randint(1, 29) | |
| date = f"{year}-{month:02d}-{day:02d}" | |
| data.append( | |
| { | |
| "id": i, | |
| "title": title, | |
| "text": body, | |
| "label": 1 if is_fake else 0, | |
| "date": date, | |
| "source_category": np.random.choice( | |
| ["political", "health", "science", "general"] | |
| ), | |
| } | |
| ) | |
| return pd.DataFrame(data) | |
| def load_dataset(self, path: Optional[Path] = None) -> pd.DataFrame: | |
| """ | |
| Load dataset from file or create synthetic one. | |
| Args: | |
| path: Path to dataset CSV file | |
| Returns: | |
| Loaded or synthetic DataFrame | |
| """ | |
| if path and path.exists(): | |
| df = pd.read_csv(path) | |
| return df | |
| # Create synthetic dataset for demonstration | |
| print("Creating synthetic dataset for demonstration...") | |
| return self.create_synthetic_dataset(n_samples=2000) | |
| def load_from_huggingface( | |
| self, dataset_name: str = "kasperdinh/fake-news-detection" | |
| ) -> pd.DataFrame: | |
| """ | |
| Load dataset from Hugging Face datasets. | |
| Args: | |
| dataset_name: Hugging Face dataset identifier | |
| Returns: | |
| DataFrame with title, text, label (0=Credible, 1=Misinformation) | |
| """ | |
| try: | |
| from datasets import load_dataset as hf_load_dataset | |
| except ImportError: | |
| raise ImportError("Install datasets: pip install datasets") | |
| print(f"Loading Hugging Face dataset: {dataset_name}") | |
| ds = hf_load_dataset(dataset_name) | |
| # Handle split structure (e.g. train, test, validation) | |
| dfs = [] | |
| for split in ds.keys(): | |
| dfs.append(ds[split].to_pandas()) | |
| df = pd.concat(dfs, ignore_index=True) | |
| return self._normalize_hf_df(df) | |
| def load_from_fakenewsnet(self, path: Optional[Path] = None) -> pd.DataFrame: | |
| """ | |
| Load FakeNewsNet.csv format (title, news_url, source_domain, tweet_num, real). | |
| real=1 means credible, real=0 means misinformation. | |
| Args: | |
| path: Path to FakeNewsNet.csv (default: project root or data/raw) | |
| Returns: | |
| DataFrame with title, text, label (0=Credible, 1=Misinformation) | |
| """ | |
| if path is None: | |
| path = PROJECT_ROOT / "FakeNewsNet.csv" | |
| if not path.exists(): | |
| path = RAW_DATA_DIR / "FakeNewsNet.csv" | |
| if not path.exists(): | |
| raise FileNotFoundError(f"FakeNewsNet.csv not found at {path}") | |
| print(f"Loading FakeNewsNet from {path}") | |
| df = pd.read_csv(path) | |
| # Normalize: real=1 -> label=0 (Credible), real=0 -> label=1 (Misinformation) | |
| if "real" in df.columns: | |
| df["label"] = (1 - df["real"]).astype(int) | |
| else: | |
| raise ValueError("FakeNewsNet must have 'real' column") | |
| if "title" in df.columns: | |
| df["text"] = df["title"].fillna("") | |
| else: | |
| raise ValueError("FakeNewsNet must have 'title' column") | |
| return df[["title", "text", "label"]].copy() | |
| def load_from_tsv(self, path: Path) -> pd.DataFrame: | |
| """ | |
| Load a single TSV file (e.g. Liar-style: id, label_str, statement, ...). | |
| Maps label: true, mostly-true -> 0 (Credible); false, pants-fire, barely-true, half-true -> 1 (Misinformation). | |
| """ | |
| df = pd.read_csv(path, sep="\t", header=None, on_bad_lines="skip") | |
| if df.shape[1] < 3: | |
| raise ValueError( | |
| f"TSV must have at least 3 columns (id, label, text). Got {df.shape[1]}" | |
| ) | |
| # Column 0: id, 1: label (string), 2: statement/text | |
| df = df.rename(columns={0: "id", 1: "label_str", 2: "text"}) | |
| df["text"] = df["text"].fillna("").astype(str) | |
| credible = ["true", "mostly-true"] | |
| df["label"] = ( | |
| df["label_str"].astype(str).str.lower().map(lambda x: 0 if x in credible else 1) | |
| ) | |
| df["title"] = df["text"].str[:200] | |
| return df[["title", "text", "label"]].copy() | |
| def load_from_data_raw(self) -> List[pd.DataFrame]: | |
| """ | |
| Load all supported data from data/raw: FakeNewsNet.csv, train.tsv, valid.tsv, test.tsv. | |
| Returns list of DataFrames (may be empty). | |
| """ | |
| dfs = [] | |
| # FakeNewsNet | |
| for name in ["FakeNewsNet.csv", "fake_news_net.csv"]: | |
| p = RAW_DATA_DIR / name | |
| if p.exists(): | |
| dfs.append(self.load_from_fakenewsnet(p)) | |
| break | |
| # TSV files (Liar-style) | |
| for name in ["train.tsv", "valid.tsv", "test.tsv"]: | |
| p = RAW_DATA_DIR / name | |
| if p.exists(): | |
| try: | |
| dfs.append(self.load_from_tsv(p)) | |
| except Exception as e: | |
| print(f"Warning: Could not load {name}: {e}") | |
| return dfs | |
| def _normalize_hf_df(self, df: pd.DataFrame) -> pd.DataFrame: | |
| """ | |
| Normalize Hugging Face dataset to title, text, label format. | |
| Handles common column names: text, title, label, fake, real, content, etc. | |
| """ | |
| out = pd.DataFrame() | |
| cols = {c.lower(): c for c in df.columns} | |
| # Resolve label column (0=Credible, 1=Misinformation) | |
| if "label" in cols: | |
| out["label"] = df[cols["label"]].astype(int) | |
| elif "fake" in cols: | |
| out["label"] = df[cols["fake"]].astype(int) | |
| elif "real" in cols: | |
| out["label"] = 1 - df[cols["real"]].astype(int) | |
| else: | |
| raise ValueError( | |
| f"Dataset must have 'label', 'fake', or 'real' column. Found: {list(df.columns)}" | |
| ) | |
| # Resolve text column | |
| if "text" in cols: | |
| out["text"] = df[cols["text"]].fillna("").astype(str) | |
| elif "content" in cols: | |
| out["text"] = df[cols["content"]].fillna("").astype(str) | |
| elif "article" in cols: | |
| out["text"] = df[cols["article"]].fillna("").astype(str) | |
| else: | |
| out["text"] = "" | |
| # Resolve title column | |
| if "title" in cols: | |
| out["title"] = df[cols["title"]].fillna("").astype(str) | |
| else: | |
| out["title"] = out["text"].str[:200] | |
| return out | |
| def clean_and_normalize(self, df: pd.DataFrame) -> pd.DataFrame: | |
| """ | |
| Clean and normalize combined dataset. | |
| - Drop rows with empty text | |
| - Normalize label to 0/1 | |
| - Remove duplicates | |
| """ | |
| df = df.copy() | |
| if "text" not in df.columns and "title" in df.columns: | |
| df["text"] = df["title"].fillna("") | |
| if "text" not in df.columns: | |
| raise ValueError("DataFrame must have 'text' or 'title' column") | |
| # Drop rows with empty or too-short text | |
| df["text"] = df["text"].fillna("").astype(str) | |
| df = df[df["text"].str.strip().str.len() >= 10].copy() | |
| # Ensure label is 0 or 1 | |
| if "label" in df.columns: | |
| df["label"] = df["label"].astype(int).clip(0, 1) | |
| if "title" not in df.columns: | |
| df["title"] = df["text"].str[:200] | |
| print(f"Cleaned: {len(df)} rows") | |
| return df | |
| def deduplicate(self, df: pd.DataFrame, text_column: str = "text") -> pd.DataFrame: | |
| """ | |
| Remove duplicate entries based on text content. | |
| Args: | |
| df: Input DataFrame | |
| text_column: Column containing text to check for duplicates | |
| Returns: | |
| Deduplicated DataFrame | |
| """ | |
| # Create hash of text for efficient deduplication | |
| df["_text_hash"] = df[text_column].apply( | |
| lambda x: hashlib.md5(str(x).encode()).hexdigest() if pd.notna(x) else None | |
| ) | |
| df_deduped = df.drop_duplicates(subset=["_text_hash"]).drop(columns=["_text_hash"]) | |
| print(f"Removed {len(df) - len(df_deduped)} duplicate entries") | |
| return df_deduped | |
| def preprocess_dataset( | |
| self, df: pd.DataFrame, text_columns: List[str] = ["title", "text"], for_bert: bool = False | |
| ) -> pd.DataFrame: | |
| """ | |
| Preprocess all text columns in the dataset. | |
| Args: | |
| df: Input DataFrame | |
| text_columns: List of columns to preprocess | |
| for_bert: Use minimal preprocessing for BERT | |
| Returns: | |
| Preprocessed DataFrame | |
| """ | |
| df = df.copy() | |
| # Ensure text column exists (for title-only datasets like FakeNewsNet) | |
| if "text" not in df.columns and "title" in df.columns: | |
| df["text"] = df["title"].fillna("") | |
| for col in text_columns: | |
| if col in df.columns: | |
| if for_bert: | |
| df[f"{col}_processed"] = df[col].apply(self.preprocessor.preprocess_for_bert) | |
| else: | |
| df[f"{col}_processed"] = df[col].apply( | |
| lambda x: self.preprocessor.preprocess(x, remove_stops=True, lemmatize=True) | |
| ) | |
| # Create combined text field (title + text) | |
| title_col = ( | |
| "title_processed" | |
| if "title_processed" in df.columns | |
| else ("title" if "title" in df.columns else None) | |
| ) | |
| text_col = ( | |
| "text_processed" | |
| if "text_processed" in df.columns | |
| else ("text" if "text" in df.columns else None) | |
| ) | |
| if title_col: | |
| df["combined_text"] = df[title_col].fillna("").astype(str) | |
| else: | |
| df["combined_text"] = "" | |
| if text_col and text_col != title_col: | |
| df["combined_text"] = df["combined_text"] + " " + df[text_col].fillna("").astype(str) | |
| df["combined_text"] = df["combined_text"].str.strip() | |
| return df | |
| def temporal_split( | |
| self, | |
| df: pd.DataFrame, | |
| date_column: str = "date", | |
| train_cutoff: str = "2023-06-01", | |
| val_cutoff: str = "2024-01-01", | |
| ) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: | |
| """ | |
| Split dataset temporally for realistic evaluation. | |
| Args: | |
| df: Input DataFrame | |
| date_column: Column containing dates | |
| train_cutoff: End date for training data | |
| val_cutoff: End date for validation data | |
| Returns: | |
| Tuple of (train_df, val_df, test_df) | |
| """ | |
| df = df.copy() | |
| df[date_column] = pd.to_datetime(df[date_column], errors="coerce") | |
| train_cutoff = pd.to_datetime(train_cutoff) | |
| val_cutoff = pd.to_datetime(val_cutoff) | |
| train_df = df[df[date_column] < train_cutoff] | |
| val_df = df[(df[date_column] >= train_cutoff) & (df[date_column] < val_cutoff)] | |
| test_df = df[df[date_column] >= val_cutoff] | |
| print(f"Temporal split: Train={len(train_df)}, Val={len(val_df)}, Test={len(test_df)}") | |
| return train_df, val_df, test_df | |
| def random_split( | |
| self, df: pd.DataFrame, stratify_column: str = "label" | |
| ) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: | |
| """ | |
| Random stratified split of dataset. | |
| Args: | |
| df: Input DataFrame | |
| stratify_column: Column to stratify by | |
| Returns: | |
| Tuple of (train_df, val_df, test_df) | |
| """ | |
| train_df, temp_df = train_test_split( | |
| df, | |
| test_size=(TEST_SIZE + VAL_SIZE), | |
| random_state=RANDOM_SEED, | |
| stratify=df[stratify_column] if stratify_column in df.columns else None, | |
| ) | |
| val_ratio = VAL_SIZE / (TEST_SIZE + VAL_SIZE) | |
| val_df, test_df = train_test_split( | |
| temp_df, | |
| test_size=1 - val_ratio, | |
| random_state=RANDOM_SEED, | |
| stratify=temp_df[stratify_column] if stratify_column in temp_df.columns else None, | |
| ) | |
| print(f"Random split: Train={len(train_df)}, Val={len(val_df)}, Test={len(test_df)}") | |
| return train_df, val_df, test_df | |
| def save_processed_data( | |
| self, | |
| train_df: pd.DataFrame, | |
| val_df: pd.DataFrame, | |
| test_df: pd.DataFrame, | |
| prefix: str = "processed", | |
| ) -> Dict[str, Path]: | |
| """ | |
| Save processed datasets to files. | |
| Args: | |
| train_df: Training DataFrame | |
| val_df: Validation DataFrame | |
| test_df: Test DataFrame | |
| prefix: Filename prefix | |
| Returns: | |
| Dictionary with paths to saved files | |
| """ | |
| paths = {} | |
| for name, df in [("train", train_df), ("val", val_df), ("test", test_df)]: | |
| path = PROCESSED_DATA_DIR / f"{prefix}_{name}.csv" | |
| df.to_csv(path, index=False) | |
| paths[name] = path | |
| print(f"Saved {name} data to {path}") | |
| return paths | |
| def prepare_data( | |
| use_synthetic: bool = False, | |
| use_hf: bool = True, | |
| use_fakenewsnet: bool = True, | |
| hf_dataset: str = "kasperdinh/fake-news-detection", | |
| ) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: | |
| """ | |
| Main function to prepare data for training. | |
| Args: | |
| use_synthetic: Use synthetic data (skip HF and FakeNewsNet) | |
| use_hf: Load from Hugging Face datasets (kasperdinh/fake-news-detection) | |
| use_fakenewsnet: Load from FakeNewsNet.csv (project root or data/raw) | |
| hf_dataset: Hugging Face dataset identifier | |
| Returns: | |
| Tuple of (train_df, val_df, test_df) | |
| """ | |
| organise_data_folders() | |
| loader = DatasetLoader() | |
| dfs = [] | |
| if use_synthetic: | |
| print("Using synthetic dataset...") | |
| df = loader.create_synthetic_dataset(n_samples=2000) | |
| dfs.append(df) | |
| else: | |
| if use_hf: | |
| try: | |
| df_hf = loader.load_from_huggingface(hf_dataset) | |
| dfs.append(df_hf) | |
| except Exception as e: | |
| print(f"Warning: Could not load Hugging Face dataset: {e}") | |
| # Data in data/raw: FakeNewsNet.csv, train.tsv, valid.tsv, test.tsv | |
| try: | |
| raw_dfs = loader.load_from_data_raw() | |
| dfs.extend(raw_dfs) | |
| except Exception as e: | |
| print(f"Warning: load_from_data_raw: {e}") | |
| # Backward compat: FakeNewsNet in project root only (data/raw already loaded) | |
| if use_fakenewsnet: | |
| try: | |
| root_csv = PROJECT_ROOT / "FakeNewsNet.csv" | |
| if root_csv.exists(): | |
| dfs.append(loader.load_from_fakenewsnet(root_csv)) | |
| except Exception as e: | |
| print(f"Warning: Could not load FakeNewsNet from root: {e}") | |
| if not dfs: | |
| print("No data loaded. Falling back to synthetic...") | |
| dfs.append(loader.create_synthetic_dataset(n_samples=2000)) | |
| df = pd.concat(dfs, ignore_index=True) | |
| # Clean and normalize | |
| df = loader.clean_and_normalize(df) | |
| # Deduplicate | |
| df = loader.deduplicate(df, text_column="text") | |
| # Preprocess | |
| df = loader.preprocess_dataset(df) | |
| # Split | |
| train_df, val_df, test_df = loader.random_split(df) | |
| # Save | |
| loader.save_processed_data(train_df, val_df, test_df) | |
| return train_df, val_df, test_df | |
| if __name__ == "__main__": | |
| train_df, val_df, test_df = prepare_data() | |
| print("\nData preparation complete!") | |
| print(f"Training samples: {len(train_df)}") | |
| print(f"Validation samples: {len(val_df)}") | |
| print(f"Test samples: {len(test_df)}") | |