Multilingual news classification system using transformer-based NLP to classify news articles across multiple categories without requiring translation.
Jupyter Notebook
13
12 commits
updated Jul 13, 2026
A production-ready multilingual NLP system that classifies Indian-language news headlines across 10 categories — no translation required.
🔗 Live demo at: huggingface.co/spaces/Jaykumardas/Multilingual_News_Classifier
🎓 Academic Project | Generative AI Assignment · Dept. of AI & ML
Chaitanya Bharathi Institute of Technology, Hyderabad · 2025–26
Guided by Mr. Panigrahi Srikanth, Assistant Professor
Billions of news articles are published daily in Indian regional languages. Most existing classifiers either:
This project solves that. We built a single unified model that reads Telugu, Malayalam, Marathi, Tamil, and Gujarati news headlines natively and classifies them into 10 predefined categories — with no translation required.
Input: "హైదరాబాద్లో క్రికెట్ టోర్నమెంట్ ప్రారంభమైంది" (Telugu)
Output: 🏏 Sports → Confidence: 91.3%
Input: "मुंबई शेअर बाजारात आज मोठी तेजी" (Marathi)
Output: 📈 Business → Confidence: 88.7%
| Property | Details |
|---|---|
| Source | IndicGLUE — ai4bharat/indic_glue |
| Subsets used | iNLTK Headlines: Telugu, Malayalam, Marathi, Tamil, Gujarati |
| Total samples | 37,069 |
| Train | 25,945 |
| Validation | 3,707 |
| Test | 7,414 |
| Split strategy | Stratified by language and label |
| Format | HuggingFace datasets — arrow / parquet |
| Language | Subset ID | Script |
|---|---|---|
| Telugu | inltkh.te | Telugu (U+0C00–U+0C7F) |
| Malayalam | inltkh.ml | Malayalam (U+0D00–U+0D7F) |
| Marathi | inltkh.mr | Devanagari (U+0900–U+097F) |
| Tamil | inltkh.ta | Tamil (U+0B80–U+0BFF) |
| Gujarati | inltkh.gu | Gujarati (U+0A80–U+0AFF) |
| # | Label | Description |
|---|---|---|
| 0 | entertainment | 🎬 Film, music, celebrity |
| 1 | business | 📈 Economy, markets, finance |
| 2 | tech | 💻 Technology, startups, gadgets |
| 3 | sports | 🏏 Cricket, football, athletics |
| 4 | state | 🗺️ State-level government and governance |
| 5 | spirituality | 🙏 Religion, culture, festivals |
| 6 | tamil-cinema | 🎞️ Tamil film industry news |
| 7 | positive | ✅ Positive sentiment stories |
| 8 | negative | ❌ Negative/critical reporting |
| 9 | neutral | ⚖️ Balanced/factual reporting |
All text is processed through a Unicode-safe pipeline before being passed to any model.
def clean_text(text: str) -> str:
text = re.sub(r"https?://\S+|www\.\S+", " ", text) # Remove URLs
text = re.sub(r"<[^>]+>", " ", text) # Remove HTML tags
text = re.sub(r"[\u200b\u200c\u200d\ufeff\u00ad]", "", text) # Zero-width chars
text = re.sub(
r"[^\w\s"
r"\u0900-\u097F" # Devanagari (Hindi, Marathi)
r"\u0C00-\u0C7F" # Telugu
r"\u0D00-\u0D7F" # Malayalam
r"\u0B80-\u0BFF" # Tamil
r"\u0A80-\u0AFF" # Gujarati
r"]", " ", text
)
return re.sub(r"\s+", " ", text).strip()
| Step | Action | Why |
|---|---|---|
| URL removal | Strip http://, www. links | Not useful for classification |
| HTML stripping | Remove <p>, <b>, etc. | Noise from scraped sources |
| Zero-width removal | Strip invisible Unicode markers | Common in Indic web text |
| Script preservation | Whitelist 5 Unicode ranges | Prevent stripping valid characters |
| Whitespace normalisation | Collapse multiple spaces | Consistent tokenizer input |
⚠️ No stemming or lemmatization — XLM-RoBERTa's SentencePiece tokenizer handles morphology natively, making these steps unnecessary and potentially harmful.
Three progressively powerful approaches are compared — each one motivated by the limitations of the previous.
The baseline establishes the minimum achievable performance without any deep learning.
Why TF-IDF with character n-grams?
Word-level TF-IDF fails on Indic languages because the same root word produces dozens of inflected forms due to agglutinative morphology. Character n-grams (1–3) capture shared sub-word patterns across these forms, making the approach far more robust across all five scripts.
TfidfVectorizer(
analyzer = 'char_wb', # Word-boundary-aware character n-grams
ngram_range = (1, 3), # Unigrams, bigrams, trigrams
max_features= 80_000, # Top 80k most discriminative n-grams
sublinear_tf= True, # log(1 + tf) — prevents common chars dominating
min_df = 2, # Ignore n-grams in < 2 documents
)
LogisticRegression(
C = 1.0,
max_iter = 1000,
class_weight = 'balanced', # Corrects for class imbalance
solver = 'lbfgs',
multi_class = 'multinomial',
)
The LSTM introduces sequential modelling — reading text word-by-word and remembering context.
Why Bidirectional?
In Indic languages, the verb often appears at the end of a sentence (subject-object-verb order). A standard left-to-right LSTM misses this. A Bidirectional LSTM reads the sentence in both directions simultaneously and combines both representations.
Architecture:
Embedding(60000+1, 128, mask_zero=True)
→ Bidirectional LSTM(128, return_sequences=True)
→ Dropout(0.3)
→ Bidirectional LSTM(64, return_sequences=True)
→ GlobalMaxPooling1D() ← captures strongest signal across all timesteps
→ Dense(128, activation='relu')
→ Dropout(0.3)
→ Dense(10, activation='softmax')
Hyperparameters:
Vocab size : 60,000 (increased for 5-language vocabulary)
Max length : 150 (longer to accommodate Indic articles)
Embedding dim: 128
Batch size : 32
Epochs : 15 (with EarlyStopping, patience=3)
Optimizer : Adam (lr=2e-4)
XLM-RoBERTa is a 125M-parameter transformer pre-trained by Meta AI on 2.5TB of text across 100 languages — including all five of our target languages.
Why XLM-RoBERTa over mBERT or IndicBERT?
XLM-R was trained on significantly more data with a larger vocabulary (250K SentencePiece tokens) and consistently outperforms mBERT on multilingual benchmarks (XNLI, XQuAD). Its SentencePiece tokenizer handles Indic scripts natively without any special character pre-processing.
Fine-tuning Strategy — Partial Layer Unfreezing:
xlm-roberta-base (12 transformer layers)
├── Layers 0–9 : FROZEN ← preserve pre-trained multilingual knowledge
├── Layers 10–11: TRAINABLE ← adapt to our news domain
└── Classifier head (linear, 10 outputs): TRAINABLE
Why freeze most layers?
- Training all 125M params on ~26k samples causes catastrophic forgetting
- Frozen layers = fast training (only ~3M params updated)
- Last 2 layers + head = sufficient for task-specific adaptation
🔗 Link to the Model and its files: huggingface.co/Jaykumardas/Multilingual_News_Model
Training Configuration:
TrainingArguments(
num_train_epochs = 5,
per_device_train_batch_size = 16,
learning_rate = 2e-5,
weight_decay = 0.01,
warmup_ratio = 0.1, # Gradual LR warmup — prevents early instability
evaluation_strategy = "epoch",
load_best_model_at_end = True,
metric_for_best_model = "f1_macro",
fp16 = True, # Mixed precision — 2× faster on GPU
)
| Model | Test Accuracy | F1 Macro | Training Time | GPU Required |
|---|---|---|---|---|
| TF-IDF + Logistic Regression | 83.84% | 77.85% | < 2 min | ❌ No |
| Bidirectional LSTM | 79.36% | 67.16% | ~14 min | ✅ Recommended |
| XLM-RoBERTa ⭐ | 86.12% | 78.75% | ~45 min | ✅ Required |
XLM-RoBERTa outperforms the baseline by +2.28% accuracy and +0.90% F1 Macro.
LSTM underperforms the baseline — see Challenges for explanation.
Test Accuracy (%) F1 Macro (%)
┌─────────────────┐ ┌─────────────────┐
TF-IDF+LR ████████████ 83.84 ████████████ 77.85 BiLSTM ███████████ 79.36 ████████ 67.16 XLM-R ⭐ █████████████ 86.12 ████████████ 78.75 └─────────────────┘ └─────────────────┘
Transformer > Traditional ML > LSTM for multilingual classification. XLM-R's pre-training on 100 languages gives it a head start that neither TF-IDF features nor an LSTM trained from scratch can match.
Character n-grams save the baseline. Word-level TF-IDF performed ~9% worse than character n-grams on Indic text due to morphological richness. This alone closed much of the gap to the LSTM.
LSTM underperformed the baseline — not a bug, but an expected outcome for multilingual data with a relatively small training set. LSTMs need far more data to learn cross-lingual representations from scratch. XLM-R has this baked in.
Topic-based categories are easiest. Sports, entertainment, and tamil-cinema have rich domain-specific vocabularies that all three models learn well. Sentiment classes are hardest because sentiment is expressed differently across languages and contexts.
5% warmup ratio was critical. Without gradual learning rate warmup, XLM-R training showed instability in the first epoch and final accuracy dropped by ~3%. Warmup protects pre-trained weights during the initial batches.
Layer freezing cut training time by ~60% with less than 1% accuracy loss vs. full fine-tuning — validating partial layer freezing as the right strategy for this dataset size.
These are real problems encountered during development, not textbook descriptions.
What happened: Early predictions were almost always "state" regardless of input. Accuracy was stuck below 20%.
Root cause: We manually wrote a {0: "entertainment", 1: "business", ...} dictionary based on guesswork. The actual integer-to-label mapping in the HuggingFace dataset was completely different.
Fix: Extract the label map directly from the dataset's ClassLabel feature:
label_names = dataset['train'].features['label'].names
id2label = {i: name for i, name in enumerate(label_names)}
Lesson: Never hardcode label mappings. Always read them from the data source.
The temptation: Merge all 10 categories into 3 broader groups (Topic / Sentiment / Other) to make the problem easier.
Why we didn't:
Decision: Keep all 10 classes. Let the model learn the harder problem.
What happened: F1 scores for positive, negative, and neutral were 10–15 points lower than topic categories. The model learned to avoid predicting these classes under uncertainty.
Mitigations applied:
class_weight='balanced' in Logistic Regression — weights each class inversely proportional to its frequencyHonest assessment: Class imbalance in sentiment classes remains the biggest gap in this system. Addressed in Future Work.
What happened: model.safetensors (~1.1GB) exceeded GitHub's 100MB file limit and HuggingFace Space's direct upload limit.
Solution — three-repo separation (industry standard pattern):
GitHub (this repo) → Source code, app.py, requirements.txt HuggingFace Model Hub → model.safetensors, config.json, tokenizer files HuggingFace Spaces → Gradio app (loads model from Hub at startup)
Why this pattern:
label_map.json Worked Locally, Failed in HuggingFace SpaceWhat happened: App ran perfectly in Kaggle. After deploying to HuggingFace Spaces, it crashed on startup with a FileNotFoundError.
Root cause: Local code used os.path.exists("./xlmr/label_map.json"). In HuggingFace Spaces, files are fetched from a remote Git repo — os.path.exists returns False for remote paths.
Fix: Use hf_hub_download() to explicitly pull the file from the Hub:
from huggingface_hub import hf_hub_download
path = hf_hub_download(repo_id="YourUsername/your-model", filename="label_map.json")
with open(path) as f:
id2label = {int(k): v for k, v in json.load(f)["id2label"].items()}
Lesson: Local file path assumptions always break in containerised deployments. Use the Hub SDK.
What happened: BiLSTM scored 79.36% vs TF-IDF's 83.84% — a deep learning model lost to a 50-year-old algorithm.
Why:
Why XLM-R doesn't have this problem: It starts with 2.5TB of pre-trained cross-lingual knowledge. Fine-tuning on 26k samples only needs to teach it domain adaptation, not language understanding from scratch.
Lesson: Raw dataset size is not the bottleneck for transformers. It is for LSTMs.
"Correct data handling beats model complexity. Deployment issues are as real as training issues. And for multilingual NLP in 2025, XLM-RoBERTa is the right starting point."
| Takeaway | Details |
|---|---|
| ✅ Data handling first | Wrong label mapping wasted 2 days of debugging |
| ✅ Transformer > LSTM for multilingual | Pre-training > architecture for low-resource multilingual tasks |
| ✅ Deployment ≠ training | 3 separate issues surfaced only after deployment |
| ✅ Honest baselines matter | A strong TF-IDF baseline revealed the LSTM was not working correctly early |
multilingual-news-classification/
│
├── 📄 app.py # Gradio UI — main entry point for HuggingFace Spaces
├── 📄 requirements.txt # All Python dependencies
├── 📄 README.md # This file
│
├── 📂 src/
│ ├── preprocess.py # Text cleaning pipeline (Unicode-safe)
│ ├── baseline_model.py # TF-IDF + Logistic Regression
│ ├── lstm_model.py # Bidirectional LSTM (TensorFlow/Keras)
│ ├── transformer_model.py # XLM-RoBERTa fine-tuning (HuggingFace Trainer)
│ ├── evaluate.py # Metrics, confusion matrix, comparison charts
│ └── main.py # CLI orchestrator — runs full pipeline
│
├── 📂 notebooks/
│ └── multilingual_news.ipynb # Complete Kaggle notebook (all phases in one)
│
├── 📂 outputs/
│ ├── model_comparison.png # Bar chart: accuracy + F1 across all models
│ ├── confusion_matrix_*.png # Per-model confusion matrices
│ └── training_curves_lstm.png # BiLSTM loss/accuracy curves
│
└── 📂 models/ # ⚠️ NOT in this repo — hosted on HuggingFace Hub
├── xlmr/ # → HuggingFace Model: YourUsername/indic-news-xlmr
│ ├── config.json
│ ├── model.safetensors
│ ├── tokenizer.json
│ └── label_map.json # id → label name mapping
└── baseline/
│ ├── tfidf_vectorizer.pkl
│ └── logistic_regression.pkl
└── lstm/
├── lstm_model.keras
└── tokenizer.pkl
# 1. Clone the repository
git clone https://github.com/YourUsername/multilingual-news-classification.git
cd multilingual-news-classification
# 2. Install dependencies
pip install -r requirements.txt
# 3. Set your HuggingFace model repo in app.py
# Change: HF_MODEL_REPO = "YourUsername/indic-news-xlmr"
# 4. Launch the app
python app.py
# → Open http://localhost:7860
# Run the full pipeline (preprocessing → all 3 models → evaluation)
python main.py --data data/news_dataset.csv --mode all --out_dir outputs/
# Run only the transformer
python main.py --mode xlmr
# Run only the baseline
python main.py --mode baseline
Visit the live HuggingFace Space — no installation required:
🔗 https://huggingface.co/spaces/YourUsername/indic-news-classifier
# requirements.txt
torch>=2.0.0
transformers>=4.35.0
datasets>=2.14.0
sentencepiece>=0.1.99
gradio>=4.0.0
scikit-learn>=1.3.0
tensorflow>=2.13.0
numpy>=1.24.0
pandas>=2.0.0
matplotlib>=3.7.0
seaborn>=0.12.0
huggingface_hub>=0.19.0
joblib>=1.3.0
The project follows a clean three-component deployment pattern:
┌─────────────────────────────────────────────────────────────────┐
│ DEPLOYMENT ARCHITECTURE │
├──────────────────┬──────────────────┬───────────────────────────┤
│ GitHub Repo │ HF Model Hub │ HuggingFace Space │
│ (Source Code) │ (Model Weights) │ (Live Gradio UI) │
├──────────────────┼──────────────────┼───────────────────────────┤
│ app.py │ config.json │ Loads model from Hub │
│ requirements.txt │ model.safetensors│ Auto-scales on CPU/GPU │
│ src/ modules │ tokenizer.json │ Public URL, zero setup │
│ README.md │ label_map.json │ Free tier supported │
└──────────────────┴──────────────────┴───────────────────────────┘
↕ git push ↕ hf_hub_download() ↕ iframe embed
| Tab | What It Does |
|---|---|
| 📰 Classify News | Single headline → predicted category + confidence score + confidence bar chart |
| 📋 Batch Classify | Up to 50 headlines at once → results table + category distribution pie chart |
| 📊 Model Comparison | Bar chart comparing all 3 models on accuracy and F1 Macro |
| 🔬 Project Details | Dataset stats, preprocessing pipeline, and results summary |
| 👥 Team | Team member cards with contributions |
Try these in the live demo:
| Language | Headline | Expected Category |
|---|---|---|
| Telugu 🇮🇳 | హైదరాబాద్లో క్రికెట్ టోర్నమెంట్ ప్రారంభమైంది; జిల్లా స్థాయి జట్లు పాల్గొంటున్నాయి | 🏏 Sports |
| Marathi 🏔️ | ముంబई శేర్ బజారులో ఈరోజు పెద్ద తేజీ; సెన్సెక్స్ 500 పాయింట్లు పెరిగింది | 📈 Business |
| Malayalam 🌴 | കേരളത്തിൽ ഇന്ന് കനത്ത മഴ; ഒൻപത് ജില്ലകളിൽ യെല്ലോ അലർട്ട് പ്രഖ്യാപിച്ചു | 🗺️ State |
| Tamil 🌺 | தமிழ்நாட்டில் புதிய தொழில்நுட்ப பூங்கா திறப்பு; ஆயிரக்கணக்கான வேலை வாய்ப்புகள் | 💻 Tech |
| Gujarati 🦁 | ગુજરાત ટીમ સ્ટેટ ક્રિકેટ ચેમ્પિયનશિપ જીતી; ખેલાડીઓ ઉત્સાહિત | 🏏 Sports |
| Priority | Improvement | Details |
|---|---|---|
| 🔴 High | Fix sentiment class imbalance | Oversample with back-translation or use focal loss |
| 🔴 High | GPU-optimised deployment | Switch from HF Spaces CPU to a GPU instance for sub-50ms inference |
| 🟡 Medium | Add more languages | Hindi, Bengali, Kannada, Odia using additional IndicGLUE subsets |
| 🟡 Medium | Larger transformer | Try xlm-roberta-large (560M params) — expected +3–5% accuracy |
| 🟡 Medium | Multi-label classification | Some headlines belong to multiple categories (e.g., State + Politics) |
| 🟢 Low | Attention visualisation | Highlight which tokens most influenced the prediction — interpretability |
| 🟢 Low | Knowledge distillation | Distil XLM-R into a smaller model for mobile/edge deployment |
| 🟢 Low | REST API | Wrap inference in a FastAPI endpoint with batch support |
|
Jay Kumar Das 160123748035Phase 1 Lead · Data Preprocessing · TF-IDF Baseline · EDA |
Siddhartha Dontula 160123748036Phase 2 Lead · BiLSTM Model · Training Curves · Evaluation |
Praneeth Reddy Ganta 160123748037Phase 3 Lead · XLM-RoBERTa · Deployment · Gradio UI |
🎓 B.Tech AI & ML · Chaitanya Bharathi Institute of Technology, Hyderabad
📧 Guided by Mr. Panigrahi Srikanth, Assistant Professor, Dept. of AIML
| 1 | IndicGLUE Dataset | HuggingFace | | 2 | Abid et al. (2019) — Gradio: Hassle-Free Sharing and Testing of ML Models | arXiv |
⭐ If this project helped you, give it a star!
Multilingual News Classification · CBIT · 2025–26
11 commits
1 commits
Jupyter Notebook
81.5%
Python
18.5%
Multilingual news classification system using transformer-based NLP to classify news articles across multiple categories without requiring translation.
Jupyter Notebook
13
12 commits
updated Jul 13, 2026
A production-ready multilingual NLP system that classifies Indian-language news headlines across 10 categories — no translation required.
🔗 Live demo at: huggingface.co/spaces/Jaykumardas/Multilingual_News_Classifier
🎓 Academic Project | Generative AI Assignment · Dept. of AI & ML
Chaitanya Bharathi Institute of Technology, Hyderabad · 2025–26
Guided by Mr. Panigrahi Srikanth, Assistant Professor
Billions of news articles are published daily in Indian regional languages. Most existing classifiers either:
This project solves that. We built a single unified model that reads Telugu, Malayalam, Marathi, Tamil, and Gujarati news headlines natively and classifies them into 10 predefined categories — with no translation required.
Input: "హైదరాబాద్లో క్రికెట్ టోర్నమెంట్ ప్రారంభమైంది" (Telugu)
Output: 🏏 Sports → Confidence: 91.3%
Input: "मुंबई शेअर बाजारात आज मोठी तेजी" (Marathi)
Output: 📈 Business → Confidence: 88.7%
| Property | Details |
|---|---|
| Source | IndicGLUE — ai4bharat/indic_glue |
| Subsets used | iNLTK Headlines: Telugu, Malayalam, Marathi, Tamil, Gujarati |
| Total samples | 37,069 |
| Train | 25,945 |
| Validation | 3,707 |
| Test | 7,414 |
| Split strategy | Stratified by language and label |
| Format | HuggingFace datasets — arrow / parquet |
| Language | Subset ID | Script |
|---|---|---|
| Telugu | inltkh.te | Telugu (U+0C00–U+0C7F) |
| Malayalam | inltkh.ml | Malayalam (U+0D00–U+0D7F) |
| Marathi | inltkh.mr | Devanagari (U+0900–U+097F) |
| Tamil | inltkh.ta | Tamil (U+0B80–U+0BFF) |
| Gujarati | inltkh.gu | Gujarati (U+0A80–U+0AFF) |
| # | Label | Description |
|---|---|---|
| 0 | entertainment | 🎬 Film, music, celebrity |
| 1 | business | 📈 Economy, markets, finance |
| 2 | tech | 💻 Technology, startups, gadgets |
| 3 | sports | 🏏 Cricket, football, athletics |
| 4 | state | 🗺️ State-level government and governance |
| 5 | spirituality | 🙏 Religion, culture, festivals |
| 6 | tamil-cinema | 🎞️ Tamil film industry news |
| 7 | positive | ✅ Positive sentiment stories |
| 8 | negative | ❌ Negative/critical reporting |
| 9 | neutral | ⚖️ Balanced/factual reporting |
All text is processed through a Unicode-safe pipeline before being passed to any model.
def clean_text(text: str) -> str:
text = re.sub(r"https?://\S+|www\.\S+", " ", text) # Remove URLs
text = re.sub(r"<[^>]+>", " ", text) # Remove HTML tags
text = re.sub(r"[\u200b\u200c\u200d\ufeff\u00ad]", "", text) # Zero-width chars
text = re.sub(
r"[^\w\s"
r"\u0900-\u097F" # Devanagari (Hindi, Marathi)
r"\u0C00-\u0C7F" # Telugu
r"\u0D00-\u0D7F" # Malayalam
r"\u0B80-\u0BFF" # Tamil
r"\u0A80-\u0AFF" # Gujarati
r"]", " ", text
)
return re.sub(r"\s+", " ", text).strip()
| Step | Action | Why |
|---|---|---|
| URL removal | Strip http://, www. links | Not useful for classification |
| HTML stripping | Remove <p>, <b>, etc. | Noise from scraped sources |
| Zero-width removal | Strip invisible Unicode markers | Common in Indic web text |
| Script preservation | Whitelist 5 Unicode ranges | Prevent stripping valid characters |
| Whitespace normalisation | Collapse multiple spaces | Consistent tokenizer input |
⚠️ No stemming or lemmatization — XLM-RoBERTa's SentencePiece tokenizer handles morphology natively, making these steps unnecessary and potentially harmful.
Three progressively powerful approaches are compared — each one motivated by the limitations of the previous.
The baseline establishes the minimum achievable performance without any deep learning.
Why TF-IDF with character n-grams?
Word-level TF-IDF fails on Indic languages because the same root word produces dozens of inflected forms due to agglutinative morphology. Character n-grams (1–3) capture shared sub-word patterns across these forms, making the approach far more robust across all five scripts.
TfidfVectorizer(
analyzer = 'char_wb', # Word-boundary-aware character n-grams
ngram_range = (1, 3), # Unigrams, bigrams, trigrams
max_features= 80_000, # Top 80k most discriminative n-grams
sublinear_tf= True, # log(1 + tf) — prevents common chars dominating
min_df = 2, # Ignore n-grams in < 2 documents
)
LogisticRegression(
C = 1.0,
max_iter = 1000,
class_weight = 'balanced', # Corrects for class imbalance
solver = 'lbfgs',
multi_class = 'multinomial',
)
The LSTM introduces sequential modelling — reading text word-by-word and remembering context.
Why Bidirectional?
In Indic languages, the verb often appears at the end of a sentence (subject-object-verb order). A standard left-to-right LSTM misses this. A Bidirectional LSTM reads the sentence in both directions simultaneously and combines both representations.
Architecture:
Embedding(60000+1, 128, mask_zero=True)
→ Bidirectional LSTM(128, return_sequences=True)
→ Dropout(0.3)
→ Bidirectional LSTM(64, return_sequences=True)
→ GlobalMaxPooling1D() ← captures strongest signal across all timesteps
→ Dense(128, activation='relu')
→ Dropout(0.3)
→ Dense(10, activation='softmax')
Hyperparameters:
Vocab size : 60,000 (increased for 5-language vocabulary)
Max length : 150 (longer to accommodate Indic articles)
Embedding dim: 128
Batch size : 32
Epochs : 15 (with EarlyStopping, patience=3)
Optimizer : Adam (lr=2e-4)
XLM-RoBERTa is a 125M-parameter transformer pre-trained by Meta AI on 2.5TB of text across 100 languages — including all five of our target languages.
Why XLM-RoBERTa over mBERT or IndicBERT?
XLM-R was trained on significantly more data with a larger vocabulary (250K SentencePiece tokens) and consistently outperforms mBERT on multilingual benchmarks (XNLI, XQuAD). Its SentencePiece tokenizer handles Indic scripts natively without any special character pre-processing.
Fine-tuning Strategy — Partial Layer Unfreezing:
xlm-roberta-base (12 transformer layers)
├── Layers 0–9 : FROZEN ← preserve pre-trained multilingual knowledge
├── Layers 10–11: TRAINABLE ← adapt to our news domain
└── Classifier head (linear, 10 outputs): TRAINABLE
Why freeze most layers?
- Training all 125M params on ~26k samples causes catastrophic forgetting
- Frozen layers = fast training (only ~3M params updated)
- Last 2 layers + head = sufficient for task-specific adaptation
🔗 Link to the Model and its files: huggingface.co/Jaykumardas/Multilingual_News_Model
Training Configuration:
TrainingArguments(
num_train_epochs = 5,
per_device_train_batch_size = 16,
learning_rate = 2e-5,
weight_decay = 0.01,
warmup_ratio = 0.1, # Gradual LR warmup — prevents early instability
evaluation_strategy = "epoch",
load_best_model_at_end = True,
metric_for_best_model = "f1_macro",
fp16 = True, # Mixed precision — 2× faster on GPU
)
| Model | Test Accuracy | F1 Macro | Training Time | GPU Required |
|---|---|---|---|---|
| TF-IDF + Logistic Regression | 83.84% | 77.85% | < 2 min | ❌ No |
| Bidirectional LSTM | 79.36% | 67.16% | ~14 min | ✅ Recommended |
| XLM-RoBERTa ⭐ | 86.12% | 78.75% | ~45 min | ✅ Required |
XLM-RoBERTa outperforms the baseline by +2.28% accuracy and +0.90% F1 Macro.
LSTM underperforms the baseline — see Challenges for explanation.
Test Accuracy (%) F1 Macro (%)
┌─────────────────┐ ┌─────────────────┐
TF-IDF+LR ████████████ 83.84 ████████████ 77.85 BiLSTM ███████████ 79.36 ████████ 67.16 XLM-R ⭐ █████████████ 86.12 ████████████ 78.75 └─────────────────┘ └─────────────────┘
Transformer > Traditional ML > LSTM for multilingual classification. XLM-R's pre-training on 100 languages gives it a head start that neither TF-IDF features nor an LSTM trained from scratch can match.
Character n-grams save the baseline. Word-level TF-IDF performed ~9% worse than character n-grams on Indic text due to morphological richness. This alone closed much of the gap to the LSTM.
LSTM underperformed the baseline — not a bug, but an expected outcome for multilingual data with a relatively small training set. LSTMs need far more data to learn cross-lingual representations from scratch. XLM-R has this baked in.
Topic-based categories are easiest. Sports, entertainment, and tamil-cinema have rich domain-specific vocabularies that all three models learn well. Sentiment classes are hardest because sentiment is expressed differently across languages and contexts.
5% warmup ratio was critical. Without gradual learning rate warmup, XLM-R training showed instability in the first epoch and final accuracy dropped by ~3%. Warmup protects pre-trained weights during the initial batches.
Layer freezing cut training time by ~60% with less than 1% accuracy loss vs. full fine-tuning — validating partial layer freezing as the right strategy for this dataset size.
These are real problems encountered during development, not textbook descriptions.
What happened: Early predictions were almost always "state" regardless of input. Accuracy was stuck below 20%.
Root cause: We manually wrote a {0: "entertainment", 1: "business", ...} dictionary based on guesswork. The actual integer-to-label mapping in the HuggingFace dataset was completely different.
Fix: Extract the label map directly from the dataset's ClassLabel feature:
label_names = dataset['train'].features['label'].names
id2label = {i: name for i, name in enumerate(label_names)}
Lesson: Never hardcode label mappings. Always read them from the data source.
The temptation: Merge all 10 categories into 3 broader groups (Topic / Sentiment / Other) to make the problem easier.
Why we didn't:
Decision: Keep all 10 classes. Let the model learn the harder problem.
What happened: F1 scores for positive, negative, and neutral were 10–15 points lower than topic categories. The model learned to avoid predicting these classes under uncertainty.
Mitigations applied:
class_weight='balanced' in Logistic Regression — weights each class inversely proportional to its frequencyHonest assessment: Class imbalance in sentiment classes remains the biggest gap in this system. Addressed in Future Work.
What happened: model.safetensors (~1.1GB) exceeded GitHub's 100MB file limit and HuggingFace Space's direct upload limit.
Solution — three-repo separation (industry standard pattern):
GitHub (this repo) → Source code, app.py, requirements.txt HuggingFace Model Hub → model.safetensors, config.json, tokenizer files HuggingFace Spaces → Gradio app (loads model from Hub at startup)
Why this pattern:
label_map.json Worked Locally, Failed in HuggingFace SpaceWhat happened: App ran perfectly in Kaggle. After deploying to HuggingFace Spaces, it crashed on startup with a FileNotFoundError.
Root cause: Local code used os.path.exists("./xlmr/label_map.json"). In HuggingFace Spaces, files are fetched from a remote Git repo — os.path.exists returns False for remote paths.
Fix: Use hf_hub_download() to explicitly pull the file from the Hub:
from huggingface_hub import hf_hub_download
path = hf_hub_download(repo_id="YourUsername/your-model", filename="label_map.json")
with open(path) as f:
id2label = {int(k): v for k, v in json.load(f)["id2label"].items()}
Lesson: Local file path assumptions always break in containerised deployments. Use the Hub SDK.
What happened: BiLSTM scored 79.36% vs TF-IDF's 83.84% — a deep learning model lost to a 50-year-old algorithm.
Why:
Why XLM-R doesn't have this problem: It starts with 2.5TB of pre-trained cross-lingual knowledge. Fine-tuning on 26k samples only needs to teach it domain adaptation, not language understanding from scratch.
Lesson: Raw dataset size is not the bottleneck for transformers. It is for LSTMs.
"Correct data handling beats model complexity. Deployment issues are as real as training issues. And for multilingual NLP in 2025, XLM-RoBERTa is the right starting point."
| Takeaway | Details |
|---|---|
| ✅ Data handling first | Wrong label mapping wasted 2 days of debugging |
| ✅ Transformer > LSTM for multilingual | Pre-training > architecture for low-resource multilingual tasks |
| ✅ Deployment ≠ training | 3 separate issues surfaced only after deployment |
| ✅ Honest baselines matter | A strong TF-IDF baseline revealed the LSTM was not working correctly early |
multilingual-news-classification/
│
├── 📄 app.py # Gradio UI — main entry point for HuggingFace Spaces
├── 📄 requirements.txt # All Python dependencies
├── 📄 README.md # This file
│
├── 📂 src/
│ ├── preprocess.py # Text cleaning pipeline (Unicode-safe)
│ ├── baseline_model.py # TF-IDF + Logistic Regression
│ ├── lstm_model.py # Bidirectional LSTM (TensorFlow/Keras)
│ ├── transformer_model.py # XLM-RoBERTa fine-tuning (HuggingFace Trainer)
│ ├── evaluate.py # Metrics, confusion matrix, comparison charts
│ └── main.py # CLI orchestrator — runs full pipeline
│
├── 📂 notebooks/
│ └── multilingual_news.ipynb # Complete Kaggle notebook (all phases in one)
│
├── 📂 outputs/
│ ├── model_comparison.png # Bar chart: accuracy + F1 across all models
│ ├── confusion_matrix_*.png # Per-model confusion matrices
│ └── training_curves_lstm.png # BiLSTM loss/accuracy curves
│
└── 📂 models/ # ⚠️ NOT in this repo — hosted on HuggingFace Hub
├── xlmr/ # → HuggingFace Model: YourUsername/indic-news-xlmr
│ ├── config.json
│ ├── model.safetensors
│ ├── tokenizer.json
│ └── label_map.json # id → label name mapping
└── baseline/
│ ├── tfidf_vectorizer.pkl
│ └── logistic_regression.pkl
└── lstm/
├── lstm_model.keras
└── tokenizer.pkl
# 1. Clone the repository
git clone https://github.com/YourUsername/multilingual-news-classification.git
cd multilingual-news-classification
# 2. Install dependencies
pip install -r requirements.txt
# 3. Set your HuggingFace model repo in app.py
# Change: HF_MODEL_REPO = "YourUsername/indic-news-xlmr"
# 4. Launch the app
python app.py
# → Open http://localhost:7860
# Run the full pipeline (preprocessing → all 3 models → evaluation)
python main.py --data data/news_dataset.csv --mode all --out_dir outputs/
# Run only the transformer
python main.py --mode xlmr
# Run only the baseline
python main.py --mode baseline
Visit the live HuggingFace Space — no installation required:
🔗 https://huggingface.co/spaces/YourUsername/indic-news-classifier
# requirements.txt
torch>=2.0.0
transformers>=4.35.0
datasets>=2.14.0
sentencepiece>=0.1.99
gradio>=4.0.0
scikit-learn>=1.3.0
tensorflow>=2.13.0
numpy>=1.24.0
pandas>=2.0.0
matplotlib>=3.7.0
seaborn>=0.12.0
huggingface_hub>=0.19.0
joblib>=1.3.0
The project follows a clean three-component deployment pattern:
┌─────────────────────────────────────────────────────────────────┐
│ DEPLOYMENT ARCHITECTURE │
├──────────────────┬──────────────────┬───────────────────────────┤
│ GitHub Repo │ HF Model Hub │ HuggingFace Space │
│ (Source Code) │ (Model Weights) │ (Live Gradio UI) │
├──────────────────┼──────────────────┼───────────────────────────┤
│ app.py │ config.json │ Loads model from Hub │
│ requirements.txt │ model.safetensors│ Auto-scales on CPU/GPU │
│ src/ modules │ tokenizer.json │ Public URL, zero setup │
│ README.md │ label_map.json │ Free tier supported │
└──────────────────┴──────────────────┴───────────────────────────┘
↕ git push ↕ hf_hub_download() ↕ iframe embed
| Tab | What It Does |
|---|---|
| 📰 Classify News | Single headline → predicted category + confidence score + confidence bar chart |
| 📋 Batch Classify | Up to 50 headlines at once → results table + category distribution pie chart |
| 📊 Model Comparison | Bar chart comparing all 3 models on accuracy and F1 Macro |
| 🔬 Project Details | Dataset stats, preprocessing pipeline, and results summary |
| 👥 Team | Team member cards with contributions |
Try these in the live demo:
| Language | Headline | Expected Category |
|---|---|---|
| Telugu 🇮🇳 | హైదరాబాద్లో క్రికెట్ టోర్నమెంట్ ప్రారంభమైంది; జిల్లా స్థాయి జట్లు పాల్గొంటున్నాయి | 🏏 Sports |
| Marathi 🏔️ | ముంబई శేర్ బజారులో ఈరోజు పెద్ద తేజీ; సెన్సెక్స్ 500 పాయింట్లు పెరిగింది | 📈 Business |
| Malayalam 🌴 | കേരളത്തിൽ ഇന്ന് കനത്ത മഴ; ഒൻപത് ജില്ലകളിൽ യെല്ലോ അലർട്ട് പ്രഖ്യാപിച്ചു | 🗺️ State |
| Tamil 🌺 | தமிழ்நாட்டில் புதிய தொழில்நுட்ப பூங்கா திறப்பு; ஆயிரக்கணக்கான வேலை வாய்ப்புகள் | 💻 Tech |
| Gujarati 🦁 | ગુજરાત ટીમ સ્ટેટ ક્રિકેટ ચેમ્પિયનશિપ જીતી; ખેલાડીઓ ઉત્સાહિત | 🏏 Sports |
| Priority | Improvement | Details |
|---|---|---|
| 🔴 High | Fix sentiment class imbalance | Oversample with back-translation or use focal loss |
| 🔴 High | GPU-optimised deployment | Switch from HF Spaces CPU to a GPU instance for sub-50ms inference |
| 🟡 Medium | Add more languages | Hindi, Bengali, Kannada, Odia using additional IndicGLUE subsets |
| 🟡 Medium | Larger transformer | Try xlm-roberta-large (560M params) — expected +3–5% accuracy |
| 🟡 Medium | Multi-label classification | Some headlines belong to multiple categories (e.g., State + Politics) |
| 🟢 Low | Attention visualisation | Highlight which tokens most influenced the prediction — interpretability |
| 🟢 Low | Knowledge distillation | Distil XLM-R into a smaller model for mobile/edge deployment |
| 🟢 Low | REST API | Wrap inference in a FastAPI endpoint with batch support |
|
Jay Kumar Das 160123748035Phase 1 Lead · Data Preprocessing · TF-IDF Baseline · EDA |
Siddhartha Dontula 160123748036Phase 2 Lead · BiLSTM Model · Training Curves · Evaluation |
Praneeth Reddy Ganta 160123748037Phase 3 Lead · XLM-RoBERTa · Deployment · Gradio UI |
🎓 B.Tech AI & ML · Chaitanya Bharathi Institute of Technology, Hyderabad
📧 Guided by Mr. Panigrahi Srikanth, Assistant Professor, Dept. of AIML
| 1 | IndicGLUE Dataset | HuggingFace | | 2 | Abid et al. (2019) — Gradio: Hassle-Free Sharing and Testing of ML Models | arXiv |
⭐ If this project helped you, give it a star!
Multilingual News Classification · CBIT · 2025–26
11 commits
1 commits
Jupyter Notebook
81.5%
Python
18.5%