Indian social media is flooded with hate speech that spans three languages simultaneously β English, Hindi (Devanagari script), and Hinglish (romanized Hindi). Traditional moderation tools fail completely in this multilingual, code-mixed environment.
Standard English-only NLP models cannot understand a sentence like "Ye log bilkul bakwaas hain, unhe yahan se nikalo" β yet this kind of language is pervasive across Indian Twitter, YouTube comments, and WhatsApp. The result: hate speech flourishes unchecked, escalating digital toxicity into real-world discrimination and violence.
This project solves that gap by fine-tuning Google's MuRIL (Multilingual Representations for Indian Languages) transformer on a custom, hand-curated dataset of code-mixed social media posts β enabling automated, accurate detection of hate speech across all three language modes simultaneously.
|
Hate speech is any form of communication β spoken, written, or behavioral β that attacks, demeans, or incites violence/discrimination against a person or group based on protected characteristics such as:
|
The datasets used in this project are publicly available on Kaggle:
| File | Description |
|---|---|
final_cleaned_dataset.csv |
Primary training set (4,860 rows) |
unified_dataset_raw.csv |
Full metadata with LLM reasoning & confidence |
combined_hate_speech_dataset.csv |
Raw 29,550-row dataset |
hard_examples.csv |
516 edge-case evaluation examples |
| Script | Language | Percentage |
|---|---|---|
| π΅ Devanagari | Native Hindi (ΰ€Ήΰ€Ώΰ€ΰ€¦ΰ₯) |
~33% |
| π’ Latin | English | ~52% |
| π‘ Romanized | Hinglish (code-mixed) | ~15% |
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β LABEL BREAKDOWN β
β β
β β
Non-Hate ββββββββββββββββββββββββ 3,989 β
β π¨ Hate ββββββββββββ 1,906 β
β β
β Total ββββββββββββββββββββββββββββββββ 5,895 β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
corrected_label/hate_label: 0 = Not Hate Speech, 1 = Hate Speech
| Split | Size | Purpose |
|---|---|---|
| π¦ Train | 4,716 |
Model learning |
| π¨ Validation | 589 |
Hyperparameter tuning |
| π₯ Test | 590 |
Final unseen evaluation |
Splits are stratified to preserve the hate/non-hate ratio across all three subsets.
Raw Social Media Data
β
βΌ
βββββββββββββββββββββββββ
β Phase 1: Data β
β Collection & β β unified_dataset_raw.csv
β Merging β
βββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Phase 2: Text β
β Preprocessing & β β final_cleaned_dataset.csv
β Cleaning β
βββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Phase 3: Hard β
β Example β β combined_training_dataset.csv
β Augmentation β
βββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Phase 4: MuRIL β
β Fine-tuning & β β final_muril_model_v2/
β Training β
βββββββββββββ¬ββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Phase 5: Gradio β
β Web App β β Live Demo Interface
β Deployment β
βββββββββββββββββββββββββ
The preprocessing pipeline was carefully designed to handle multilingual text without destroying Hindi Devanagari matras (diacritics) β a common failure point of naive text cleaning pipelines.
| Step | Operation | Reason |
|---|---|---|
| 1οΈβ£ | Remove URLs | Noise reduction |
| 2οΈβ£ | Remove @username handles |
Privacy + noise |
| 3οΈβ£ | Remove RT artifacts |
Twitter-specific cleanup |
| 4οΈβ£ | Remove ASCII punctuation (safely) | Preserves Devanagari script |
| 5οΈβ£ | Collapse extra whitespace | Normalization |
| 6οΈβ£ | Lowercase conversion | Normalizes English/Hinglish, safely ignores Hindi |
π View Preprocessing Code
import pandas as pd
import re
import string
df = pd.read_csv("unified_dataset_raw.csv")
def clean_multilingual_text(text):
if not isinstance(text, str):
return ""
# Remove URLs
text = re.sub(r"http\S+|www\S+|https\S+", "", text, flags=re.MULTILINE)
# Remove @handles
text = re.sub(r"\@\w+", "", text)
# Remove RT artifacts
text = re.sub(r"\bRT\b", "", text)
# Safely remove ASCII punctuation (preserves Hindi matras)
punctuation_pattern = f"[{re.escape(string.punctuation)}]"
text = re.sub(punctuation_pattern, " ", text)
# Collapse whitespace
text = re.sub(r"\s+", " ", text).strip()
# Lowercase (safe for Hindi Devanagari)
text = text.lower()
return text
df['clean_text'] = df['text'].apply(clean_multilingual_text)
df = df[df['clean_text'].str.strip().astype(bool)]
df_final = df[['clean_text', 'corrected_label', 'language', 'text']].copy()
df_final.to_csv("final_cleaned_dataset.csv", index=False)To improve classifier robustness on subtle and implicit hate, the dataset was augmented with carefully curated hard examples that the model might otherwise miss.
| Type | Description |
|---|---|
| π΄ Implicit Threats | Hate framed as hypotheticals or warnings |
| π‘ Coded Hate Speech | Dog-whistle language and in-group slurs |
| π΅ Ambiguous Hinglish | Phrases with dual interpretations |
| π’ Caste-based Insults | Slurs specific to Indian caste discrimination |
π View Augmentation Code
import pandas as pd
df_original = pd.read_csv("final_cleaned_dataset.csv")
df_hard = pd.read_csv("hard_examples.csv", on_bad_lines='warn')
df_combined = pd.concat([df_original, df_hard], ignore_index=True)
df_combined = df_combined.sample(frac=1, random_state=42).reset_index(drop=True)
df_combined.to_csv("combined_training_dataset.csv", index=False)
print(f"Total rows in combined dataset: {len(df_combined)}")MuRIL (Multilingual Representations for Indian Languages) is a transformer model developed by Google, pre-trained on 17 Indian languages and their transliterated variants. Unlike standard BERT or mBERT, MuRIL natively understands:
- β Devanagari Hindi script
- β Romanized / transliterated Hindi
- β Hinglish code-mixed text
- β Cross-script language switching within a single sentence
| Hyperparameter | Value |
|---|---|
| Base Model | google/muril-base-cased |
| Max Token Length | 128 |
| Learning Rate | 2e-5 |
| Batch Size | 16 |
| Epochs | 3 |
| Weight Decay | 0.01 |
| Best Model Metric | Macro F1 Score |
The model is evaluated using Macro F1 Score, which gives equal weight to both classes regardless of class imbalance β ensuring the hate speech class isn't overshadowed by the larger non-hate class.
Precision = TP / (TP + FP)
Recall = TP / (TP + FN)
F1 = 2 Γ (Precision Γ Recall) / (Precision + Recall)
Macro F1 = (F1_hate + F1_non-hate) / 2
π View Training Code
from transformers import AutoModelForSequenceClassification, TrainingArguments, Trainer
from transformers import AutoTokenizer
import evaluate, numpy as np
model_name = "google/muril-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
# Tokenization
def tokenize_function(examples):
return tokenizer(examples["clean_text"], padding="max_length",
truncation=True, max_length=128)
tokenized_datasets = hf_dataset.map(tokenize_function, batched=True)
tokenized_datasets = tokenized_datasets.remove_columns(["clean_text", "text", "language"])
tokenized_datasets = tokenized_datasets.rename_column("corrected_label", "labels")
tokenized_datasets.set_format("torch")
# Metrics
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return {
"accuracy": accuracy_metric.compute(predictions=predictions, references=labels)["accuracy"],
"f1_macro": f1_metric.compute(predictions=predictions, references=labels, average="macro")["f1"],
"precision_macro": precision_metric.compute(predictions=predictions, references=labels, average="macro")["precision"],
"recall_macro": recall_metric.compute(predictions=predictions, references=labels, average="macro")["recall"],
}
# Training
training_args = TrainingArguments(
output_dir="./muril_hate_speech_v2",
eval_strategy="epoch", save_strategy="epoch",
learning_rate=2e-5, per_device_train_batch_size=16,
num_train_epochs=3, weight_decay=0.01,
load_best_model_at_end=True, metric_for_best_model="f1_macro"
)
trainer = Trainer(model=model, args=training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
compute_metrics=compute_metrics)
trainer.train()| Class | Precision | Recall | F1-Score | Support |
|---|---|---|---|---|
| β Non-Hate (0) | 0.86 |
0.90 |
0.88 |
399 |
| π¨ Hate (1) | 0.76 |
0.70 |
0.73 |
191 |
| β | β | β | β | β |
| π― Accuracy | 0.83 |
590 | ||
| π Macro Avg | 0.81 |
0.80 |
0.80 |
590 |
| π Weighted Avg | 0.83 |
0.83 |
0.83 |
590 |
Predicted
Non-Hate β Hate
βββββββββββββΌββββββββββ
Actual Non-Hate β 359 β 40 β
Hate β 57 β 134 β
βββββββββββββββββββββββ
A fully functional Gradio web interface was built on top of the fine-tuned MuRIL model, enabling real-world hate speech detection across three input modes:
| Feature | Description |
|---|---|
| Raw Text | Paste a comment or social media post directly |
| Web Article | Enter a URL β the app scrapes and analyzes the full article |
| Document | Upload .txt or .pdf files for sentence-level analysis |
| Flag & Report | Users can flag incorrect predictions, saved to Google Drive |
| Drive Auto-sync | Flagged cases are automatically backed up to Google Drive |
For documents and articles, the app splits content into individual sentences and flags each one independently β producing a hate speech density report showing exactly which sentences are toxic and with what confidence.
π View App Code (Gradio Interface)
import gradio as gr
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
# Load model from Drive
tokenizer = AutoTokenizer.from_pretrained(drive_path)
model = AutoModelForSequenceClassification.from_pretrained(drive_path)
model.eval()
def analyze_single_text(text):
inputs = tokenizer(text, return_tensors="pt", truncation=True,
padding=True, max_length=128)
with torch.no_grad():
logits = model(**inputs).logits
pred = logits.argmax(axis=-1).item()
probs = torch.nn.functional.softmax(logits, dim=-1)
conf = probs[0][pred].item() * 100
return pred, conf
# Launch app
demo.launch(share=True)| Finding | Detail |
|---|---|
| Multilingual Capability | Successfully interprets English, Devanagari Hindi, and Hinglish code-mixed text |
| Strong Baseline | 83% accuracy and Macro F1-Score of 0.80 on unseen test data |
| Safe Moderation Bias | High Non-Hate precision β innocent users are rarely flagged incorrectly |
| Future Work | Improved detection of implicit hate, sarcasm, and subtle toxic expressions |
# Clone the repository
git clone https://github.com/your-username/multilingual-hate-speech-detection.git
cd multilingual-hate-speech-detection
# Install dependencies
pip install transformers datasets evaluate scikit-learn accelerate torch gradio
pip install beautifulsoup4 PyPDF2 pandas numpy matplotlib seaborn| Resource | Link |
|---|---|
| Dataset on Kaggle | |
| MuRIL Base Model | |
| Training Notebook | Google Colab (see /notebooks) |
