Marwan-Tamer commited on
Commit
c1fb7b1
·
0 Parent(s):

feat: complete module 1 language detection

Browse files
.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()