Spaces:
Sleeping
Sleeping
Commit ·
c1fb7b1
0
Parent(s):
feat: complete module 1 language detection
Browse files- .gitignore +20 -0
- Fetch_Data.py +10 -0
- README.md +38 -0
- Requirements.md +54 -0
- notebooks/module_1_language_detection.ipynb +83 -0
- reports/module_1_language_detection/metrics_summary.json +38 -0
- reports/module_1_language_detection/test_classification_report.csv +24 -0
- reports/module_1_language_detection/test_classification_report.txt +26 -0
- reports/module_1_language_detection/test_confusion_matrix.csv +21 -0
- reports/module_1_language_detection/validation_classification_report.csv +24 -0
- reports/module_1_language_detection/validation_classification_report.txt +26 -0
- reports/module_1_language_detection/validation_confusion_matrix.csv +21 -0
- requirements.txt +6 -0
- src/models/language_classifier.py +263 -0
- src/models/language_detector_ui.py +46 -0
- src/models/test_ui.py +5 -0
.gitignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python environments and caches
|
| 2 |
+
.venv/
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.py[cod]
|
| 5 |
+
.ipynb_checkpoints/
|
| 6 |
+
|
| 7 |
+
# Local data generated from public datasets
|
| 8 |
+
data/*.csv
|
| 9 |
+
|
| 10 |
+
# Trained model artifacts
|
| 11 |
+
src/models/*.pkl
|
| 12 |
+
src/models/saved_emotion_model/
|
| 13 |
+
src/models/saved_*/
|
| 14 |
+
checkpoints/
|
| 15 |
+
runs/
|
| 16 |
+
|
| 17 |
+
# Local secrets and editor files
|
| 18 |
+
.env
|
| 19 |
+
*.log
|
| 20 |
+
.vscode/
|
Fetch_Data.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
from datasets import load_dataset
|
| 3 |
+
|
| 4 |
+
dataset = load_dataset("papluca/language-identification")
|
| 5 |
+
|
| 6 |
+
dataset['train'].to_pandas().to_csv("data/lang_train.csv", index=False)
|
| 7 |
+
dataset['validation'].to_pandas().to_csv("data/lang_val.csv", index=False)
|
| 8 |
+
dataset['test'].to_pandas().to_csv("data/lang_test.csv", index=False)
|
| 9 |
+
|
| 10 |
+
print("Data is ready")
|
README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# RAG-Based Mental Health Support Chatbot
|
| 2 |
+
|
| 3 |
+
This repository contains the module work for the NLP final project.
|
| 4 |
+
|
| 5 |
+
## Module 1: Language Detection
|
| 6 |
+
|
| 7 |
+
The language detector is implemented with traditional NLP:
|
| 8 |
+
|
| 9 |
+
- Vectorizer: character-level TF-IDF with `char_wb` n-grams from 2 to 4 characters
|
| 10 |
+
- Classifier: Multinomial Naive Bayes
|
| 11 |
+
- Dataset: `papluca/language-identification`
|
| 12 |
+
- Supported languages: Arabic, Bulgarian, German, Greek, English, Spanish, French, Hindi, Italian, Japanese, Dutch, Polish, Portuguese, Russian, Swahili, Thai, Turkish, Urdu, Vietnamese, Chinese
|
| 13 |
+
|
| 14 |
+
### Train and Evaluate
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
.\.venv\Scripts\python.exe src\models\language_classifier.py
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
This trains the model, saves it to `src/models/saved_lang_model.pkl`, and writes reports to:
|
| 21 |
+
|
| 22 |
+
```text
|
| 23 |
+
reports/module_1_language_detection/
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### Run the UI
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
.\.venv\Scripts\python.exe src\models\language_detector_ui.py
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
The UI returns the detected language, confidence, and whether the prediction passed the confidence threshold.
|
| 33 |
+
|
| 34 |
+
### Install Dependencies
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
|
| 38 |
+
```
|
Requirements.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
NLP Final Task 2026
|
| 2 |
+
RAG-Based Mental Health Support Chatbot
|
| 3 |
+
Introduction
|
| 4 |
+
This project requires designing and implementing a Retrieval-Augmented Generation (RAG)
|
| 5 |
+
based chatbot for a mental health support system. The chatbot must provide grounded,
|
| 6 |
+
empathetic, and context-aware responses to queries related to anxiety, depression, stress,
|
| 7 |
+
and crisis support.
|
| 8 |
+
The system integrates multiple NLP techniques into a single pipeline, where each component
|
| 9 |
+
plays a critical role in improving overall system performance.
|
| 10 |
+
Prerequisites
|
| 11 |
+
Before starting this project, complete the following course:
|
| 12 |
+
• Retrieval Augmented Generation (RAG) @ deeplearning.ai
|
| 13 |
+
ONLY the first 4 modules. 5th module is extra.
|
| 14 |
+
System Overview
|
| 15 |
+
The chatbot system is composed of multiple interconnected modules. Each module
|
| 16 |
+
contributes directly to the performance of the final system, making this a fully integrated,
|
| 17 |
+
end-to-end project rather than isolated tasks.
|
| 18 |
+
Project Modules
|
| 19 |
+
1) Language Detection
|
| 20 |
+
Build a multi-class classifier using Traditional NLP such as: Count Vectorizer or TF-IDF
|
| 21 |
+
Vectorizer. Train ML model to classify the language of the user’s question. This module is very
|
| 22 |
+
important for the entire system for searching right in the knowledgebase in addition to replying in
|
| 23 |
+
the same language and so on.
|
| 24 |
+
2) Emotion Classifier
|
| 25 |
+
Build a multi-class classifier using either Recurrent Neural Networks OR Transformers. Feel
|
| 26 |
+
free to choose as you like. Train model to classify emotion of the user’s question. This module is
|
| 27 |
+
very important for optimizing the final chatbot response depending on the user’s emotion to be
|
| 28 |
+
able to handle his different emotions. This is a key factor in the success of the system.
|
| 29 |
+
3) Intent Classifier
|
| 30 |
+
Build a multi-class classifier using either zero shot or few shot LLM prompting to classify the
|
| 31 |
+
intent of the user’s question. Intent is one of the following: greeting, goodbye, gratitude,
|
| 32 |
+
asking_mental_health_question, out_of_scope. This module is very important for routing the
|
| 33 |
+
entire system to the best route for answering. For example: - If the user is greeting, there is no need for RAG and answer directly. - If the user is asking a mental health question, you must use RAG to answer.
|
| 34 |
+
4) Q&A RAG
|
| 35 |
+
Build RAG pipeline using the mental health counseling dataset to answer the upcoming user’s
|
| 36 |
+
questions. Feel free to use the suitable framework you prefer or build from scratch, as you like.
|
| 37 |
+
You need to follow these components: - For vector database, use free cloud qdrant. - For embeddings, use senetence transformer. - For LLM, use free groq account and gpt-oss-120b or gpt-oss-20b.
|
| 38 |
+
Datasets
|
| 39 |
+
1) Language Identification Dataset
|
| 40 |
+
2) Emotion Dataset
|
| 41 |
+
3) Mental Health Counseling Conversations
|
| 42 |
+
Guidelines
|
| 43 |
+
• You must use python.
|
| 44 |
+
• Choose the most suitable data pre-processing techniques.
|
| 45 |
+
• Use Flask or FastAPI or any suitable web framework to deploy the model locally.
|
| 46 |
+
Deliverables
|
| 47 |
+
• Four module-specific notebooks.
|
| 48 |
+
• Deployment scripts.
|
| 49 |
+
• Any additional files/documentation you need.
|
| 50 |
+
Notes
|
| 51 |
+
• In the assessment phase, you’ll be asked to run your models locally, furthermore, you’ll
|
| 52 |
+
be asked in any technical decision/implementation you’ve made, so be well prepared,
|
| 53 |
+
and avoid overcomplicated approaches you don’t fully grasp.
|
| 54 |
+
• Early submission doesn’t affect your grade, take your time.
|
notebooks/module_1_language_detection.ipynb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Module 1: Language Detection\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook documents the language detection module for the RAG-based mental health support chatbot. The goal is to classify the user question language so later modules can route retrieval and response generation correctly."
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "markdown",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"## Approach\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"- Use traditional NLP as required by the project brief.\n",
|
| 19 |
+
"- Extract character-level TF-IDF features because language identity is strongly reflected in scripts, accents, and short character patterns.\n",
|
| 20 |
+
"- Train a Multinomial Naive Bayes classifier because it is fast, explainable, and strong for sparse text features."
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"cell_type": "code",
|
| 25 |
+
"execution_count": null,
|
| 26 |
+
"metadata": {},
|
| 27 |
+
"outputs": [],
|
| 28 |
+
"source": [
|
| 29 |
+
"import sys\n",
|
| 30 |
+
"from pathlib import Path\n",
|
| 31 |
+
"\n",
|
| 32 |
+
"PROJECT_ROOT = Path.cwd().resolve().parent if Path.cwd().name == 'notebooks' else Path.cwd().resolve()\n",
|
| 33 |
+
"sys.path.append(str(PROJECT_ROOT / 'src' / 'models'))\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"from language_classifier import LanguageDetector, DATA_DIR"
|
| 36 |
+
]
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"cell_type": "code",
|
| 40 |
+
"execution_count": null,
|
| 41 |
+
"metadata": {},
|
| 42 |
+
"outputs": [],
|
| 43 |
+
"source": [
|
| 44 |
+
"detector = LanguageDetector()\n",
|
| 45 |
+
"results = detector.train(\n",
|
| 46 |
+
" train_path=DATA_DIR / 'lang_train.csv',\n",
|
| 47 |
+
" validation_path=DATA_DIR / 'lang_val.csv',\n",
|
| 48 |
+
" test_path=DATA_DIR / 'lang_test.csv',\n",
|
| 49 |
+
")\n",
|
| 50 |
+
"results['test']['accuracy']"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"cell_type": "code",
|
| 55 |
+
"execution_count": null,
|
| 56 |
+
"metadata": {},
|
| 57 |
+
"outputs": [],
|
| 58 |
+
"source": [
|
| 59 |
+
"samples = [\n",
|
| 60 |
+
" 'I feel anxious and need help.',\n",
|
| 61 |
+
" 'انا اشعر بالقلق واحتاج الى المساعدة.',\n",
|
| 62 |
+
" \"Je me sens stresse aujourd'hui.\",\n",
|
| 63 |
+
"]\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"for sample in samples:\n",
|
| 66 |
+
" print(sample, '->', detector.predict_with_confidence(sample))"
|
| 67 |
+
]
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
"metadata": {
|
| 71 |
+
"kernelspec": {
|
| 72 |
+
"display_name": "Python 3",
|
| 73 |
+
"language": "python",
|
| 74 |
+
"name": "python3"
|
| 75 |
+
},
|
| 76 |
+
"language_info": {
|
| 77 |
+
"name": "python",
|
| 78 |
+
"pygments_lexer": "ipython3"
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"nbformat": 4,
|
| 82 |
+
"nbformat_minor": 5
|
| 83 |
+
}
|
reports/module_1_language_detection/metrics_summary.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "Character n-gram TF-IDF + Multinomial Naive Bayes",
|
| 3 |
+
"vectorizer": {
|
| 4 |
+
"analyzer": "char_wb",
|
| 5 |
+
"ngram_range": [
|
| 6 |
+
2,
|
| 7 |
+
4
|
| 8 |
+
],
|
| 9 |
+
"max_features": 50000,
|
| 10 |
+
"lowercase": true
|
| 11 |
+
},
|
| 12 |
+
"classifier": "MultinomialNB",
|
| 13 |
+
"confidence_threshold": 0.65,
|
| 14 |
+
"validation_accuracy": 0.9935,
|
| 15 |
+
"test_accuracy": 0.994,
|
| 16 |
+
"languages": {
|
| 17 |
+
"ar": "Arabic",
|
| 18 |
+
"bg": "Bulgarian",
|
| 19 |
+
"de": "German",
|
| 20 |
+
"el": "Greek",
|
| 21 |
+
"en": "English",
|
| 22 |
+
"es": "Spanish",
|
| 23 |
+
"fr": "French",
|
| 24 |
+
"hi": "Hindi",
|
| 25 |
+
"it": "Italian",
|
| 26 |
+
"ja": "Japanese",
|
| 27 |
+
"nl": "Dutch",
|
| 28 |
+
"pl": "Polish",
|
| 29 |
+
"pt": "Portuguese",
|
| 30 |
+
"ru": "Russian",
|
| 31 |
+
"sw": "Swahili",
|
| 32 |
+
"th": "Thai",
|
| 33 |
+
"tr": "Turkish",
|
| 34 |
+
"ur": "Urdu",
|
| 35 |
+
"vi": "Vietnamese",
|
| 36 |
+
"zh": "Chinese"
|
| 37 |
+
}
|
| 38 |
+
}
|
reports/module_1_language_detection/test_classification_report.csv
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
,precision,recall,f1-score,support
|
| 2 |
+
ar,1.0,0.998,0.998998998998999,500.0
|
| 3 |
+
bg,0.998003992015968,1.0,0.999000999000999,500.0
|
| 4 |
+
de,1.0,1.0,1.0,500.0
|
| 5 |
+
el,1.0,1.0,1.0,500.0
|
| 6 |
+
en,0.984251968503937,1.0,0.9920634920634921,500.0
|
| 7 |
+
es,0.9784735812133072,1.0,0.9891196834817013,500.0
|
| 8 |
+
fr,0.9920634920634921,1.0,0.9960159362549801,500.0
|
| 9 |
+
hi,1.0,0.968,0.983739837398374,500.0
|
| 10 |
+
it,0.996,0.996,0.996,500.0
|
| 11 |
+
ja,1.0,1.0,1.0,500.0
|
| 12 |
+
nl,1.0,0.996,0.9979959919839679,500.0
|
| 13 |
+
pl,1.0,0.99,0.9949748743718593,500.0
|
| 14 |
+
pt,0.9979508196721312,0.974,0.9858299595141701,500.0
|
| 15 |
+
ru,1.0,0.998,0.998998998998999,500.0
|
| 16 |
+
sw,0.9397363465160076,0.998,0.967992240543162,500.0
|
| 17 |
+
th,1.0,0.998,0.998998998998999,500.0
|
| 18 |
+
tr,0.998003992015968,1.0,0.999000999000999,500.0
|
| 19 |
+
ur,1.0,0.964,0.9816700610997964,500.0
|
| 20 |
+
vi,1.0,1.0,1.0,500.0
|
| 21 |
+
zh,1.0,1.0,1.0,500.0
|
| 22 |
+
accuracy,0.994,0.994,0.994,0.994
|
| 23 |
+
macro avg,0.9942242096000407,0.994,0.9940200535855249,10000.0
|
| 24 |
+
weighted avg,0.9942242096000405,0.994,0.994020053585525,10000.0
|
reports/module_1_language_detection/test_classification_report.txt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
precision recall f1-score support
|
| 2 |
+
|
| 3 |
+
ar 1.00 1.00 1.00 500
|
| 4 |
+
bg 1.00 1.00 1.00 500
|
| 5 |
+
de 1.00 1.00 1.00 500
|
| 6 |
+
el 1.00 1.00 1.00 500
|
| 7 |
+
en 0.98 1.00 0.99 500
|
| 8 |
+
es 0.98 1.00 0.99 500
|
| 9 |
+
fr 0.99 1.00 1.00 500
|
| 10 |
+
hi 1.00 0.97 0.98 500
|
| 11 |
+
it 1.00 1.00 1.00 500
|
| 12 |
+
ja 1.00 1.00 1.00 500
|
| 13 |
+
nl 1.00 1.00 1.00 500
|
| 14 |
+
pl 1.00 0.99 0.99 500
|
| 15 |
+
pt 1.00 0.97 0.99 500
|
| 16 |
+
ru 1.00 1.00 1.00 500
|
| 17 |
+
sw 0.94 1.00 0.97 500
|
| 18 |
+
th 1.00 1.00 1.00 500
|
| 19 |
+
tr 1.00 1.00 1.00 500
|
| 20 |
+
ur 1.00 0.96 0.98 500
|
| 21 |
+
vi 1.00 1.00 1.00 500
|
| 22 |
+
zh 1.00 1.00 1.00 500
|
| 23 |
+
|
| 24 |
+
accuracy 0.99 10000
|
| 25 |
+
macro avg 0.99 0.99 0.99 10000
|
| 26 |
+
weighted avg 0.99 0.99 0.99 10000
|
reports/module_1_language_detection/test_confusion_matrix.csv
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
,ar,bg,de,el,en,es,fr,hi,it,ja,nl,pl,pt,ru,sw,th,tr,ur,vi,zh
|
| 2 |
+
ar,499,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 3 |
+
bg,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 4 |
+
de,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 5 |
+
el,0,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 6 |
+
en,0,0,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 7 |
+
es,0,0,0,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 8 |
+
fr,0,0,0,0,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 9 |
+
hi,0,0,0,0,1,0,0,484,0,0,0,0,0,0,15,0,0,0,0,0
|
| 10 |
+
it,0,0,0,0,1,1,0,0,498,0,0,0,0,0,0,0,0,0,0,0
|
| 11 |
+
ja,0,0,0,0,0,0,0,0,0,500,0,0,0,0,0,0,0,0,0,0
|
| 12 |
+
nl,0,0,0,0,2,0,0,0,0,0,498,0,0,0,0,0,0,0,0,0
|
| 13 |
+
pl,0,0,0,0,1,0,1,0,1,0,0,495,1,0,1,0,0,0,0,0
|
| 14 |
+
pt,0,0,0,0,1,10,2,0,0,0,0,0,487,0,0,0,0,0,0,0
|
| 15 |
+
ru,0,1,0,0,0,0,0,0,0,0,0,0,0,499,0,0,0,0,0,0
|
| 16 |
+
sw,0,0,0,0,0,0,0,0,1,0,0,0,0,0,499,0,0,0,0,0
|
| 17 |
+
th,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,499,0,0,0,0
|
| 18 |
+
tr,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500,0,0,0
|
| 19 |
+
ur,0,0,0,0,1,0,0,0,0,0,0,0,0,0,16,0,1,482,0,0
|
| 20 |
+
vi,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500,0
|
| 21 |
+
zh,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500
|
reports/module_1_language_detection/validation_classification_report.csv
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
,precision,recall,f1-score,support
|
| 2 |
+
ar,1.0,0.994,0.9969909729187563,500.0
|
| 3 |
+
bg,0.9960159362549801,1.0,0.998003992015968,500.0
|
| 4 |
+
de,0.998,0.998,0.998,500.0
|
| 5 |
+
el,1.0,1.0,1.0,500.0
|
| 6 |
+
en,0.9803921568627451,1.0,0.9900990099009901,500.0
|
| 7 |
+
es,0.9881188118811881,0.998,0.9930348258706467,500.0
|
| 8 |
+
fr,0.9940357852882704,1.0,0.9970089730807578,500.0
|
| 9 |
+
hi,1.0,0.952,0.9754098360655737,500.0
|
| 10 |
+
it,0.9900793650793651,0.998,0.9940239043824701,500.0
|
| 11 |
+
ja,0.998,0.998,0.998,500.0
|
| 12 |
+
nl,0.9940357852882704,1.0,0.9970089730807578,500.0
|
| 13 |
+
pl,1.0,0.996,0.9979959919839679,500.0
|
| 14 |
+
pt,0.9979674796747967,0.982,0.9899193548387096,500.0
|
| 15 |
+
ru,1.0,0.996,0.9979959919839679,500.0
|
| 16 |
+
sw,0.9416195856873822,1.0,0.9699321047526673,500.0
|
| 17 |
+
th,1.0,0.998,0.998998998998999,500.0
|
| 18 |
+
tr,0.9960159362549801,1.0,0.998003992015968,500.0
|
| 19 |
+
ur,1.0,0.968,0.983739837398374,500.0
|
| 20 |
+
vi,1.0,1.0,1.0,500.0
|
| 21 |
+
zh,1.0,0.992,0.9959839357429718,500.0
|
| 22 |
+
accuracy,0.9935,0.9935,0.9935,0.9935
|
| 23 |
+
macro avg,0.9937140421135989,0.9935,0.9935075347515774,10000.0
|
| 24 |
+
weighted avg,0.9937140421135989,0.9935,0.9935075347515773,10000.0
|
reports/module_1_language_detection/validation_classification_report.txt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
precision recall f1-score support
|
| 2 |
+
|
| 3 |
+
ar 1.00 0.99 1.00 500
|
| 4 |
+
bg 1.00 1.00 1.00 500
|
| 5 |
+
de 1.00 1.00 1.00 500
|
| 6 |
+
el 1.00 1.00 1.00 500
|
| 7 |
+
en 0.98 1.00 0.99 500
|
| 8 |
+
es 0.99 1.00 0.99 500
|
| 9 |
+
fr 0.99 1.00 1.00 500
|
| 10 |
+
hi 1.00 0.95 0.98 500
|
| 11 |
+
it 0.99 1.00 0.99 500
|
| 12 |
+
ja 1.00 1.00 1.00 500
|
| 13 |
+
nl 0.99 1.00 1.00 500
|
| 14 |
+
pl 1.00 1.00 1.00 500
|
| 15 |
+
pt 1.00 0.98 0.99 500
|
| 16 |
+
ru 1.00 1.00 1.00 500
|
| 17 |
+
sw 0.94 1.00 0.97 500
|
| 18 |
+
th 1.00 1.00 1.00 500
|
| 19 |
+
tr 1.00 1.00 1.00 500
|
| 20 |
+
ur 1.00 0.97 0.98 500
|
| 21 |
+
vi 1.00 1.00 1.00 500
|
| 22 |
+
zh 1.00 0.99 1.00 500
|
| 23 |
+
|
| 24 |
+
accuracy 0.99 10000
|
| 25 |
+
macro avg 0.99 0.99 0.99 10000
|
| 26 |
+
weighted avg 0.99 0.99 0.99 10000
|
reports/module_1_language_detection/validation_confusion_matrix.csv
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
,ar,bg,de,el,en,es,fr,hi,it,ja,nl,pl,pt,ru,sw,th,tr,ur,vi,zh
|
| 2 |
+
ar,497,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 3 |
+
bg,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 4 |
+
de,0,0,499,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 5 |
+
el,0,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 6 |
+
en,0,0,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 7 |
+
es,0,0,0,0,0,499,0,0,1,0,0,0,0,0,0,0,0,0,0,0
|
| 8 |
+
fr,0,0,0,0,0,0,500,0,0,0,0,0,0,0,0,0,0,0,0,0
|
| 9 |
+
hi,0,0,0,0,1,0,0,476,0,0,3,0,0,0,18,0,2,0,0,0
|
| 10 |
+
it,0,0,0,0,1,0,0,0,499,0,0,0,0,0,0,0,0,0,0,0
|
| 11 |
+
ja,0,0,0,0,0,0,0,0,1,499,0,0,0,0,0,0,0,0,0,0
|
| 12 |
+
nl,0,0,0,0,0,0,0,0,0,0,500,0,0,0,0,0,0,0,0,0
|
| 13 |
+
pl,0,0,0,0,1,0,0,0,1,0,0,498,0,0,0,0,0,0,0,0
|
| 14 |
+
pt,0,0,0,0,1,6,0,0,2,0,0,0,491,0,0,0,0,0,0,0
|
| 15 |
+
ru,0,2,0,0,0,0,0,0,0,0,0,0,0,498,0,0,0,0,0,0
|
| 16 |
+
sw,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500,0,0,0,0,0
|
| 17 |
+
th,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,499,0,0,0,0
|
| 18 |
+
tr,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500,0,0,0
|
| 19 |
+
ur,0,0,0,0,2,0,1,0,0,0,0,0,0,0,13,0,0,484,0,0
|
| 20 |
+
vi,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,500,0
|
| 21 |
+
zh,0,0,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,496
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pandas==3.0.3
|
| 2 |
+
scikit-learn==1.9.0
|
| 3 |
+
joblib==1.5.3
|
| 4 |
+
gradio==6.18.0
|
| 5 |
+
datasets==5.0.0
|
| 6 |
+
jupyter
|
src/models/language_classifier.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
import argparse
|
| 3 |
+
import json
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from typing import Any
|
| 7 |
+
import joblib
|
| 8 |
+
import pandas as pd
|
| 9 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 10 |
+
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
|
| 11 |
+
from sklearn.naive_bayes import MultinomialNB
|
| 12 |
+
from sklearn.pipeline import Pipeline
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
| 16 |
+
DATA_DIR = PROJECT_ROOT / "data"
|
| 17 |
+
MODEL_DIR = PROJECT_ROOT / "src" / "models"
|
| 18 |
+
REPORTS_DIR = PROJECT_ROOT / "reports" / "module_1_language_detection"
|
| 19 |
+
DEFAULT_MODEL_PATH = MODEL_DIR / "saved_lang_model.pkl"
|
| 20 |
+
|
| 21 |
+
LANGUAGE_NAMES = {
|
| 22 |
+
"ar": "Arabic",
|
| 23 |
+
"bg": "Bulgarian",
|
| 24 |
+
"de": "German",
|
| 25 |
+
"el": "Greek",
|
| 26 |
+
"en": "English",
|
| 27 |
+
"es": "Spanish",
|
| 28 |
+
"fr": "French",
|
| 29 |
+
"hi": "Hindi",
|
| 30 |
+
"it": "Italian",
|
| 31 |
+
"ja": "Japanese",
|
| 32 |
+
"nl": "Dutch",
|
| 33 |
+
"pl": "Polish",
|
| 34 |
+
"pt": "Portuguese",
|
| 35 |
+
"ru": "Russian",
|
| 36 |
+
"sw": "Swahili",
|
| 37 |
+
"th": "Thai",
|
| 38 |
+
"tr": "Turkish",
|
| 39 |
+
"ur": "Urdu",
|
| 40 |
+
"vi": "Vietnamese",
|
| 41 |
+
"zh": "Chinese",
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class LanguageDetector:
|
| 46 |
+
"""Traditional NLP language detector using character TF-IDF and Naive Bayes."""
|
| 47 |
+
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
model_path: str | Path = DEFAULT_MODEL_PATH,
|
| 51 |
+
confidence_threshold: float = 0.65,
|
| 52 |
+
) -> None:
|
| 53 |
+
self.model_path = Path(model_path)
|
| 54 |
+
self.confidence_threshold = confidence_threshold
|
| 55 |
+
self.pipeline = self._build_pipeline()
|
| 56 |
+
|
| 57 |
+
@staticmethod
|
| 58 |
+
def _build_pipeline() -> Pipeline:
|
| 59 |
+
return Pipeline(
|
| 60 |
+
[
|
| 61 |
+
(
|
| 62 |
+
"tfidf",
|
| 63 |
+
TfidfVectorizer(
|
| 64 |
+
analyzer="char_wb",
|
| 65 |
+
ngram_range=(2, 4),
|
| 66 |
+
max_features=50000,
|
| 67 |
+
lowercase=True,
|
| 68 |
+
),
|
| 69 |
+
),
|
| 70 |
+
("clf", MultinomialNB()),
|
| 71 |
+
]
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
@staticmethod
|
| 75 |
+
def _load_dataset(path: str | Path) -> pd.DataFrame:
|
| 76 |
+
df = pd.read_csv(path)
|
| 77 |
+
required_columns = {"text", "labels"}
|
| 78 |
+
missing_columns = required_columns.difference(df.columns)
|
| 79 |
+
|
| 80 |
+
if missing_columns:
|
| 81 |
+
raise ValueError(f"{path} is missing columns: {sorted(missing_columns)}")
|
| 82 |
+
|
| 83 |
+
df = df.dropna(subset=["text", "labels"]).copy()
|
| 84 |
+
df["text"] = df["text"].astype(str).str.strip()
|
| 85 |
+
df = df[df["text"] != ""]
|
| 86 |
+
return df
|
| 87 |
+
|
| 88 |
+
def train(
|
| 89 |
+
self,
|
| 90 |
+
train_path: str | Path = DATA_DIR / "lang_train.csv",
|
| 91 |
+
validation_path: str | Path = DATA_DIR / "lang_val.csv",
|
| 92 |
+
test_path: str | Path = DATA_DIR / "lang_test.csv",
|
| 93 |
+
) -> dict[str, Any]:
|
| 94 |
+
train_df = self._load_dataset(train_path)
|
| 95 |
+
validation_df = self._load_dataset(validation_path)
|
| 96 |
+
test_df = self._load_dataset(test_path)
|
| 97 |
+
|
| 98 |
+
print("Training character n-gram TF-IDF language detector...")
|
| 99 |
+
self.pipeline.fit(train_df["text"], train_df["labels"])
|
| 100 |
+
|
| 101 |
+
validation_metrics = self.evaluate(validation_df, "validation")
|
| 102 |
+
test_metrics = self.evaluate(test_df, "test")
|
| 103 |
+
|
| 104 |
+
self.save_model()
|
| 105 |
+
self.save_reports(validation_metrics, test_metrics)
|
| 106 |
+
|
| 107 |
+
return {
|
| 108 |
+
"validation": validation_metrics,
|
| 109 |
+
"test": test_metrics,
|
| 110 |
+
"model_path": str(self.model_path),
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
def evaluate(self, df: pd.DataFrame, split_name: str) -> dict[str, Any]:
|
| 114 |
+
predictions = self.pipeline.predict(df["text"])
|
| 115 |
+
labels = sorted(df["labels"].unique())
|
| 116 |
+
report_dict = classification_report(
|
| 117 |
+
df["labels"],
|
| 118 |
+
predictions,
|
| 119 |
+
labels=labels,
|
| 120 |
+
output_dict=True,
|
| 121 |
+
zero_division=0,
|
| 122 |
+
)
|
| 123 |
+
report_text = classification_report(
|
| 124 |
+
df["labels"],
|
| 125 |
+
predictions,
|
| 126 |
+
labels=labels,
|
| 127 |
+
zero_division=0,
|
| 128 |
+
)
|
| 129 |
+
matrix = confusion_matrix(df["labels"], predictions, labels=labels)
|
| 130 |
+
|
| 131 |
+
accuracy = accuracy_score(df["labels"], predictions)
|
| 132 |
+
print(f"{split_name.title()} accuracy: {accuracy * 100:.2f}%")
|
| 133 |
+
|
| 134 |
+
return {
|
| 135 |
+
"split": split_name,
|
| 136 |
+
"accuracy": accuracy,
|
| 137 |
+
"labels": labels,
|
| 138 |
+
"classification_report": report_dict,
|
| 139 |
+
"classification_report_text": report_text,
|
| 140 |
+
"confusion_matrix": matrix.tolist(),
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
def save_model(self) -> None:
|
| 144 |
+
self.model_path.parent.mkdir(parents=True, exist_ok=True)
|
| 145 |
+
joblib.dump(self.pipeline, self.model_path)
|
| 146 |
+
print(f"Saved model to {self.model_path}")
|
| 147 |
+
|
| 148 |
+
def save_reports(self, validation_metrics: dict[str, Any], test_metrics: dict[str, Any]) -> None:
|
| 149 |
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
| 150 |
+
|
| 151 |
+
summary = {
|
| 152 |
+
"model": "Character n-gram TF-IDF + Multinomial Naive Bayes",
|
| 153 |
+
"vectorizer": {
|
| 154 |
+
"analyzer": "char_wb",
|
| 155 |
+
"ngram_range": [2, 4],
|
| 156 |
+
"max_features": 50000,
|
| 157 |
+
"lowercase": True,
|
| 158 |
+
},
|
| 159 |
+
"classifier": "MultinomialNB",
|
| 160 |
+
"confidence_threshold": self.confidence_threshold,
|
| 161 |
+
"validation_accuracy": validation_metrics["accuracy"],
|
| 162 |
+
"test_accuracy": test_metrics["accuracy"],
|
| 163 |
+
"languages": LANGUAGE_NAMES,
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
(REPORTS_DIR / "metrics_summary.json").write_text(
|
| 167 |
+
json.dumps(summary, indent=2),
|
| 168 |
+
encoding="utf-8",
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
for metrics in (validation_metrics, test_metrics):
|
| 172 |
+
split = metrics["split"]
|
| 173 |
+
labels = metrics["labels"]
|
| 174 |
+
|
| 175 |
+
(REPORTS_DIR / f"{split}_classification_report.txt").write_text(
|
| 176 |
+
metrics["classification_report_text"],
|
| 177 |
+
encoding="utf-8",
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
pd.DataFrame(metrics["classification_report"]).transpose().to_csv(
|
| 181 |
+
REPORTS_DIR / f"{split}_classification_report.csv",
|
| 182 |
+
encoding="utf-8",
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
pd.DataFrame(
|
| 186 |
+
metrics["confusion_matrix"],
|
| 187 |
+
index=labels,
|
| 188 |
+
columns=labels,
|
| 189 |
+
).to_csv(REPORTS_DIR / f"{split}_confusion_matrix.csv", encoding="utf-8")
|
| 190 |
+
|
| 191 |
+
print(f"Saved evaluation reports to {REPORTS_DIR}")
|
| 192 |
+
|
| 193 |
+
def load_model(self) -> None:
|
| 194 |
+
if not self.model_path.exists():
|
| 195 |
+
raise FileNotFoundError(
|
| 196 |
+
f"Model not found at {self.model_path}. Run training first."
|
| 197 |
+
)
|
| 198 |
+
self.pipeline = joblib.load(self.model_path)
|
| 199 |
+
|
| 200 |
+
def predict(self, text: str) -> str:
|
| 201 |
+
return self.predict_with_confidence(text)["language_code"]
|
| 202 |
+
|
| 203 |
+
def predict_with_confidence(self, text: str) -> dict[str, Any]:
|
| 204 |
+
clean_text = text.strip()
|
| 205 |
+
if len(clean_text) < 3:
|
| 206 |
+
return {
|
| 207 |
+
"language_code": "unknown",
|
| 208 |
+
"language_name": "Unknown",
|
| 209 |
+
"confidence": 0.0,
|
| 210 |
+
"is_confident": False,
|
| 211 |
+
"message": "Please enter at least 3 characters.",
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
probabilities = self.pipeline.predict_proba([clean_text])[0]
|
| 215 |
+
best_index = int(probabilities.argmax())
|
| 216 |
+
language_code = str(self.pipeline.classes_[best_index])
|
| 217 |
+
confidence = float(probabilities[best_index])
|
| 218 |
+
|
| 219 |
+
return {
|
| 220 |
+
"language_code": language_code,
|
| 221 |
+
"language_name": LANGUAGE_NAMES.get(language_code, language_code.upper()),
|
| 222 |
+
"confidence": confidence,
|
| 223 |
+
"is_confident": confidence >= self.confidence_threshold,
|
| 224 |
+
"message": None,
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def _configure_console() -> None:
|
| 229 |
+
if hasattr(sys.stdout, "reconfigure"):
|
| 230 |
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def parse_args() -> argparse.Namespace:
|
| 234 |
+
parser = argparse.ArgumentParser(description="Train and evaluate Module 1 language detector.")
|
| 235 |
+
parser.add_argument("--train-path", default=DATA_DIR / "lang_train.csv", type=Path)
|
| 236 |
+
parser.add_argument("--validation-path", default=DATA_DIR / "lang_val.csv", type=Path)
|
| 237 |
+
parser.add_argument("--test-path", default=DATA_DIR / "lang_test.csv", type=Path)
|
| 238 |
+
parser.add_argument("--model-path", default=DEFAULT_MODEL_PATH, type=Path)
|
| 239 |
+
return parser.parse_args()
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
if __name__ == "__main__":
|
| 243 |
+
_configure_console()
|
| 244 |
+
args = parse_args()
|
| 245 |
+
|
| 246 |
+
detector = LanguageDetector(model_path=args.model_path)
|
| 247 |
+
results = detector.train(args.train_path, args.validation_path, args.test_path)
|
| 248 |
+
|
| 249 |
+
sample_texts = [
|
| 250 |
+
"I feel anxious and need someone to talk to.",
|
| 251 |
+
"أنا أشعر بالقلق وأحتاج إلى المساعدة.",
|
| 252 |
+
"Je me sens stresse aujourd'hui.",
|
| 253 |
+
]
|
| 254 |
+
|
| 255 |
+
print("\nSample predictions:")
|
| 256 |
+
for sample in sample_texts:
|
| 257 |
+
prediction = detector.predict_with_confidence(sample)
|
| 258 |
+
print(
|
| 259 |
+
f"- {sample!r} -> {prediction['language_name']} "
|
| 260 |
+
f"({prediction['confidence'] * 100:.1f}%)"
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
print(f"\nFinal test accuracy: {results['test']['accuracy'] * 100:.2f}%")
|
src/models/language_detector_ui.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
import gradio as gr
|
| 4 |
+
|
| 5 |
+
from language_classifier import DEFAULT_MODEL_PATH, LanguageDetector
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
MODEL_PATH = Path(DEFAULT_MODEL_PATH)
|
| 9 |
+
detector = LanguageDetector(model_path=MODEL_PATH)
|
| 10 |
+
detector.load_model()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def predict_language(text: str) -> dict[str, str | float]:
|
| 14 |
+
prediction = detector.predict_with_confidence(text or "")
|
| 15 |
+
|
| 16 |
+
if prediction["message"]:
|
| 17 |
+
return {
|
| 18 |
+
"language": prediction["language_name"],
|
| 19 |
+
"confidence": prediction["confidence"],
|
| 20 |
+
"status": prediction["message"],
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
status = "Confident" if prediction["is_confident"] else "Uncertain"
|
| 24 |
+
return {
|
| 25 |
+
"language": prediction["language_name"],
|
| 26 |
+
"confidence": round(prediction["confidence"], 4),
|
| 27 |
+
"status": status,
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
interface = gr.Interface(
|
| 32 |
+
fn=predict_language,
|
| 33 |
+
inputs=gr.Textbox(
|
| 34 |
+
lines=5,
|
| 35 |
+
placeholder="Type a question in any supported language...",
|
| 36 |
+
label="User Question",
|
| 37 |
+
),
|
| 38 |
+
outputs=gr.JSON(label="Detection Result"),
|
| 39 |
+
title="Module 1: Language Detection",
|
| 40 |
+
description="Character n-gram TF-IDF language classifier for routing chatbot queries.",
|
| 41 |
+
flagging_mode="never",
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
if __name__ == "__main__":
|
| 46 |
+
interface.launch()
|
src/models/test_ui.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from language_detector_ui import interface, predict_language
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
if __name__ == "__main__":
|
| 5 |
+
interface.launch()
|