Spaces:
Running on Zero
Running on Zero
Download src/train_gpu.py from kaliputra1/deviputra-hate-speech-detector: direct link, hf CLI and curl.
- Browser
- Download file 8.9 kB
-
https://huggingface.co/spaces/kaliputra1/deviputra-hate-speech-detector/resolve/main/src/train_gpu.py
- Command line
-
hf download hf://spaces/kaliputra1/deviputra-hate-speech-detector/src/train_gpu.py
-
curl -L -o train_gpu.py https://huggingface.co/spaces/kaliputra1/deviputra-hate-speech-detector/resolve/main/src/train_gpu.py
8.9 kB
| """ | |
| DEVIPUTRA AI // PURE PYTORCH & TRANSFORMERS ENGINE | |
| Fine-tunes Multilingual HingHate Transformer on Code-Mixed Hinglish Slangs & Hate Speech. | |
| Features: | |
| - Pure Python & PyTorch (zero external DLL blocks from pandas/sklearn) | |
| - Supports PyTorch CUDA / Tensor Cores / CPU execution | |
| - Stratified 85/15 Train-Val split with F1-Score & Accuracy tracking | |
| - Saves production-ready checkpoint into models/hing_hate_bert_rtx5050/ | |
| """ | |
| import os | |
| import sys | |
| import csv | |
| import random | |
| from typing import Dict, List, Tuple | |
| if hasattr(sys.stdout, 'reconfigure'): | |
| try: | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| except Exception: | |
| pass | |
| import torch | |
| LABEL_TO_ID = {"Normal": 0, "Offensive": 1, "Hate Speech": 2} | |
| ID_TO_LABEL = {0: "Normal", 1: "Offensive", 2: "Hate Speech"} | |
| class HinglishTextDataset(torch.utils.data.Dataset): | |
| def __init__(self, texts: List[str], labels: List[int], tokenizer, max_length: int = 128): | |
| self.texts = list(texts) | |
| self.labels = list(labels) | |
| self.tokenizer = tokenizer | |
| self.max_length = max_length | |
| def __len__(self): | |
| return len(self.texts) | |
| def __getitem__(self, idx): | |
| text = str(self.texts[idx]) | |
| label = self.labels[idx] | |
| encoding = self.tokenizer( | |
| text, | |
| truncation=True, | |
| padding='max_length', | |
| max_length=self.max_length, | |
| return_tensors='pt' | |
| ) | |
| return { | |
| 'input_ids': encoding['input_ids'].squeeze(0), | |
| 'attention_mask': encoding['attention_mask'].squeeze(0), | |
| 'label': torch.tensor(label, dtype=torch.long) | |
| } | |
| def load_data_clean(filepath: str) -> Tuple[List[str], List[int]]: | |
| texts, labels = [], [] | |
| with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: | |
| reader = csv.reader(f) | |
| header = next(reader, None) | |
| for row in reader: | |
| if len(row) >= 2: | |
| txt = row[0].strip() | |
| lbl = row[1].strip() | |
| if txt and lbl in LABEL_TO_ID: | |
| texts.append(txt) | |
| labels.append(LABEL_TO_ID[lbl]) | |
| return texts, labels | |
| def stratified_split(texts: List[str], labels: List[int], val_ratio: float = 0.15, seed: int = 42): | |
| random.seed(seed) | |
| class_groups = {0: [], 1: [], 2: []} | |
| for t, l in zip(texts, labels): | |
| class_groups[l].append(t) | |
| train_texts, train_labels = [], [] | |
| val_texts, val_labels = [], [] | |
| for l, group in class_groups.items(): | |
| random.shuffle(group) | |
| val_count = max(1, int(len(group) * val_ratio)) | |
| val_texts.extend(group[:val_count]) | |
| val_labels.extend([l] * val_count) | |
| train_texts.extend(group[val_count:]) | |
| train_labels.extend([l] * (len(group) - val_count)) | |
| # Shuffle training items | |
| combined = list(zip(train_texts, train_labels)) | |
| random.shuffle(combined) | |
| train_texts, train_labels = zip(*combined) | |
| return list(train_texts), list(train_labels), val_texts, val_labels | |
| def compute_metrics(preds: List[int], targets: List[int]) -> Dict[str, float]: | |
| total = len(targets) | |
| if total == 0: | |
| return {"accuracy": 0.0, "f1": 0.0} | |
| correct = sum(1 for p, y in zip(preds, targets) if p == y) | |
| acc = correct / total | |
| # Weighted F1 | |
| f1_weighted = 0.0 | |
| for c in [0, 1, 2]: | |
| tp = sum(1 for p, y in zip(preds, targets) if p == c and y == c) | |
| fp = sum(1 for p, y in zip(preds, targets) if p == c and y != c) | |
| fn = sum(1 for p, y in zip(preds, targets) if p != c and y == c) | |
| support = sum(1 for y in targets if y == c) | |
| prec = tp / (tp + fp) if (tp + fp) > 0 else 0.0 | |
| rec = tp / (tp + fn) if (tp + fn) > 0 else 0.0 | |
| f1_class = (2 * prec * rec / (prec + rec)) if (prec + rec) > 0 else 0.0 | |
| f1_weighted += f1_class * (support / total) | |
| return {"accuracy": round(acc, 4), "f1": round(f1_weighted, 4)} | |
| def train_rtx5050( | |
| data_path: str = "data/master_train_dataset.csv", | |
| base_model_name: str = "l3cube-pune/hing-bert", | |
| output_dir: str = "models/hing_hate_bert_rtx5050", | |
| epochs: int = 3, | |
| batch_size: int = 16, | |
| lr: float = 2e-5 | |
| ): | |
| print("=" * 70) | |
| print("DEVIPUTRA AI // HINGLISH TRANSFORMER FINE-TUNING PIPELINE") | |
| print("=" * 70) | |
| # 1. Device and Architecture Detection | |
| if torch.cuda.is_available(): | |
| gpu_name = torch.cuda.get_device_name(0) | |
| vram_gb = torch.cuda.get_device_properties(0).total_memory / (1024**3) | |
| device = torch.device("cuda:0") | |
| print(f"[GPU ENGINE ACTIVE]: {gpu_name} ({vram_gb:.2f} GB VRAM)") | |
| else: | |
| device = torch.device("cpu") | |
| print("[DEVICE]: CPU (Optimized for lightweight inference)") | |
| # 2. Load Dataset using pure csv module | |
| if not os.path.exists(data_path): | |
| data_path = os.path.join("data", "sample.csv") | |
| print(f"Loading Hinglish training corpus: {data_path}...") | |
| texts, labels = load_data_clean(data_path) | |
| print(f"Total samples: {len(texts)} samples loaded.") | |
| train_texts, train_labels, val_texts, val_labels = stratified_split(texts, labels, val_ratio=0.15) | |
| print(f"Split: {len(train_texts)} train items | {len(val_texts)} validation items.") | |
| # 3. Load HuggingFace Tokenizer & Model | |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification | |
| from torch.optim import AdamW | |
| print(f"Loading transformer backbone ({base_model_name})...") | |
| tokenizer = AutoTokenizer.from_pretrained(base_model_name) | |
| model = AutoModelForSequenceClassification.from_pretrained( | |
| base_model_name, | |
| num_labels=3, | |
| id2label=ID_TO_LABEL, | |
| label2id=LABEL_TO_ID | |
| ) | |
| model.to(device) | |
| train_ds = HinglishTextDataset(train_texts, train_labels, tokenizer) | |
| val_ds = HinglishTextDataset(val_texts, val_labels, tokenizer) | |
| train_loader = torch.utils.data.DataLoader( | |
| train_ds, batch_size=batch_size, shuffle=True | |
| ) | |
| val_loader = torch.utils.data.DataLoader( | |
| val_ds, batch_size=batch_size, shuffle=False | |
| ) | |
| optimizer = AdamW(model.parameters(), lr=lr, weight_decay=0.01) | |
| best_val_f1 = 0.0 | |
| os.makedirs(output_dir, exist_ok=True) | |
| print("\nSTARTING TRAINING LOOPS...") | |
| for epoch in range(1, epochs + 1): | |
| model.train() | |
| total_loss = 0.0 | |
| for step, batch in enumerate(train_loader): | |
| input_ids = batch['input_ids'].to(device) | |
| attention_mask = batch['attention_mask'].to(device) | |
| target_labels = batch['label'].to(device) | |
| optimizer.zero_grad() | |
| outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=target_labels) | |
| loss = outputs.loss | |
| loss.backward() | |
| optimizer.step() | |
| total_loss += loss.item() | |
| if (step + 1) % 10 == 0 or (step + 1) == len(train_loader): | |
| print(f" Epoch [{epoch}/{epochs}] | Step [{step+1}/{len(train_loader)}] | Batch Loss: {loss.item():.4f}") | |
| avg_train_loss = total_loss / len(train_loader) | |
| # Validation Phase | |
| model.eval() | |
| val_preds, val_targets = [], [] | |
| with torch.no_grad(): | |
| for batch in val_loader: | |
| input_ids = batch['input_ids'].to(device) | |
| attention_mask = batch['attention_mask'].to(device) | |
| target_labels = batch['label'] | |
| outputs = model(input_ids=input_ids, attention_mask=attention_mask) | |
| preds = torch.argmax(outputs.logits, dim=1).cpu().tolist() | |
| val_preds.extend(preds) | |
| val_targets.extend(target_labels.tolist()) | |
| metrics = compute_metrics(val_preds, val_targets) | |
| val_acc = metrics["accuracy"] | |
| val_f1 = metrics["f1"] | |
| print(f"\nEPOCH {epoch} SUMMARY:") | |
| print(f" Train Loss : {avg_train_loss:.4f}") | |
| print(f" Validation Acc : {val_acc * 100:.2f}%") | |
| print(f" Validation F1 : {val_f1:.4f}") | |
| print("-" * 50) | |
| # Save Best Checkpoint | |
| if val_f1 >= best_val_f1: | |
| best_val_f1 = val_f1 | |
| print(f"Saving checkpoint to: '{output_dir}'...") | |
| model.save_pretrained(output_dir) | |
| tokenizer.save_pretrained(output_dir) | |
| print("\nTRAINING COMPLETE!") | |
| print(f"Best Validation F1: {best_val_f1:.4f}") | |
| print(f"Model saved to: {output_dir}") | |
| print("=" * 70) | |
| if __name__ == "__main__": | |
| import argparse | |
| parser = argparse.ArgumentParser(description="Hinglish Fine-Tuning Script") | |
| parser.add_argument("--epochs", type=int, default=2) | |
| parser.add_argument("--batch_size", type=int, default=16) | |
| parser.add_argument("--lr", type=float, default=2e-5) | |
| args = parser.parse_args() | |
| train_rtx5050(epochs=args.epochs, batch_size=args.batch_size, lr=args.lr) | |