Spaces:
Sleeping
Sleeping
| import json | |
| import torch | |
| from torch.utils.data import Dataset, DataLoader | |
| from transformers import RobertaTokenizer, RobertaForSequenceClassification | |
| from torch.optim import AdamW | |
| from sklearn.model_selection import train_test_split | |
| from tqdm import tqdm | |
| class SpamDataset(Dataset): | |
| def __init__(self, texts, labels, tokenizer, max_length=256): | |
| self.encodings = tokenizer(texts, truncation=True, padding=True, max_length=max_length) | |
| self.labels = labels | |
| def __getitem__(self, idx): | |
| item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()} | |
| item['labels'] = torch.tensor(self.labels[idx]) | |
| return item | |
| def __len__(self): | |
| return len(self.labels) | |
| texts = [] | |
| labels = [] | |
| # Load scampatterns.json (Kenyan-specific: KRA, police, M-Pesa, etc.) | |
| print("Loading scampatterns.json...") | |
| with open('scampatterns.json', 'r', encoding='utf-8') as f: | |
| data1 = json.load(f) | |
| for item in data1: | |
| texts.append(item['text']) | |
| labels.append(1 if item['label'] == 'scam' else 0) | |
| print(f" scampatterns.json: {len(data1)} samples") | |
| # Load training_data.json (generic English scam/non-scam) | |
| print("Loading training_data.json...") | |
| with open('training_data.json', 'r', encoding='utf-8') as f: | |
| data2 = json.load(f) | |
| for item in data2: | |
| texts.append(item['text']) | |
| # Normalize: "scam" -> 1, everything else (non-scam, legitimate) -> 0 | |
| labels.append(1 if item['label'] == 'scam' else 0) | |
| print(f" training_data.json: {len(data2)} samples") | |
| total_scam = sum(labels) | |
| total_legit = len(labels) - total_scam | |
| print(f"\nTotal samples: {len(texts)} | Scam: {total_scam} | Legit: {total_legit}") | |
| # Split data | |
| train_texts, val_texts, train_labels, val_labels = train_test_split( | |
| texts, labels, test_size=0.2, random_state=42, stratify=labels | |
| ) | |
| # Initialize tokenizer and model | |
| tokenizer = RobertaTokenizer.from_pretrained('roberta-base') | |
| model = RobertaForSequenceClassification.from_pretrained('roberta-base', num_labels=2) | |
| # Create datasets | |
| train_dataset = SpamDataset(train_texts, train_labels, tokenizer) | |
| val_dataset = SpamDataset(val_texts, val_labels, tokenizer) | |
| train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True) | |
| val_loader = DataLoader(val_dataset, batch_size=8) | |
| # Class weights to handle imbalance | |
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') | |
| print(f"\nUsing device: {device}") | |
| scam_ratio = total_legit / total_scam if total_scam > 0 else 1.0 | |
| class_weights = torch.tensor([1.0, scam_ratio], dtype=torch.float).to(device) | |
| print(f"Class weights: legit=1.0, scam={scam_ratio:.2f}") | |
| model.to(device) | |
| optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) | |
| loss_fn = torch.nn.CrossEntropyLoss(weight=class_weights) | |
| # Training loop | |
| epochs = 4 | |
| best_val_acc = 0.0 | |
| for epoch in range(epochs): | |
| model.train() | |
| total_loss = 0 | |
| for batch in tqdm(train_loader, desc=f'Epoch {epoch+1}/{epochs}'): | |
| optimizer.zero_grad() | |
| input_ids = batch['input_ids'].to(device) | |
| attention_mask = batch['attention_mask'].to(device) | |
| batch_labels = batch['labels'].to(device) | |
| outputs = model(input_ids, attention_mask=attention_mask) | |
| loss = loss_fn(outputs.logits, batch_labels) | |
| total_loss += loss.item() | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| optimizer.step() | |
| # Validation | |
| model.eval() | |
| correct = 0 | |
| total = 0 | |
| true_positives = 0 | |
| false_negatives = 0 | |
| false_positives = 0 | |
| with torch.no_grad(): | |
| for batch in val_loader: | |
| input_ids = batch['input_ids'].to(device) | |
| attention_mask = batch['attention_mask'].to(device) | |
| batch_labels = batch['labels'].to(device) | |
| outputs = model(input_ids, attention_mask=attention_mask) | |
| predictions = torch.argmax(outputs.logits, dim=-1) | |
| correct += (predictions == batch_labels).sum().item() | |
| total += batch_labels.size(0) | |
| # Track scam-specific metrics | |
| true_positives += ((predictions == 1) & (batch_labels == 1)).sum().item() | |
| false_negatives += ((predictions == 0) & (batch_labels == 1)).sum().item() | |
| false_positives += ((predictions == 1) & (batch_labels == 0)).sum().item() | |
| accuracy = correct / total | |
| recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0 | |
| precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0 | |
| f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0 | |
| print(f'Epoch {epoch+1} | Loss: {total_loss/len(train_loader):.4f} | ' | |
| f'Acc: {accuracy:.4f} | Recall(scam): {recall:.4f} | ' | |
| f'Precision(scam): {precision:.4f} | F1: {f1:.4f}') | |
| if accuracy > best_val_acc: | |
| best_val_acc = accuracy | |
| model.save_pretrained('./spam_classifier_model') | |
| tokenizer.save_pretrained('./spam_classifier_model') | |
| print(f' -> Best model saved (acc={accuracy:.4f})') | |
| print(f'\nTraining complete. Best accuracy: {best_val_acc:.4f}') | |
| print('Model saved to ./spam_classifier_model') | |