# ============================================================ # English Learning Assistant — TinyKAN-Distill # Author: Mouna Khlifi — University of Kairouan, Tunisia # ============================================================ import gradio as gr import torch import torch.nn as nn import numpy as np from sentence_transformers import SentenceTransformer import sys, os, random import google.generativeai as genai os.system("pip install -q git+https://github.com/KindXiaoming/pykan.git google-generativeai") sys.path.insert(0, '/tmp/pykan') from kan import KAN class TinyKANDistilled(nn.Module): def __init__(self, input_dim=9, grid=3): super().__init__() self.kan_main = KAN(width=[[input_dim,0],[16,0],[8,0],[1,0]], grid=grid) self.linear_branch = nn.Sequential(nn.Linear(input_dim,8), nn.GELU(), nn.Linear(8,1)) self.gate = nn.Sequential(nn.Linear(input_dim,8), nn.ReLU(), nn.Linear(8,2), nn.Softmax(dim=1)) self.skip = nn.Linear(input_dim, 1) def forward(self, x): k = self.kan_main(x); l = self.linear_branch(x) g = self.gate(x) return (g * torch.cat([k,l],dim=1)).sum(dim=1,keepdim=True) + 0.05*self.skip(x) def get_gates(self, x): with torch.no_grad(): return self.gate(x).cpu().numpy()[0] DEVICE = "cpu" encoders = { "MiniLM": SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2"), "MPNet": SentenceTransformer("sentence-transformers/all-mpnet-base-v2"), "RoBERTa": SentenceTransformer("sentence-transformers/stsb-roberta-base"), } tinykan = TinyKANDistilled(9, 3) tinykan.load_state_dict(torch.load("tinykan_cosine.pth", map_location=DEVICE, weights_only=False)) tinykan.eval() GEMINI_KEY = os.environ.get("GEMINI_API_KEY", "") gemini_model = None if GEMINI_KEY: genai.configure(api_key=GEMINI_KEY) gemini_model = genai.GenerativeModel("gemini-1.5-flash") EXERCISES = { "translation": [ {"fr": "Le soleil se lève à l'est.", "en": "The sun rises in the east.", "level": "A1"}, {"fr": "Il fait beau aujourd'hui.", "en": "The weather is nice today.", "level": "A1"}, {"fr": "J'ai un chat et un chien.", "en": "I have a cat and a dog.", "level": "A1"}, {"fr": "J'apprends l'anglais depuis deux ans.", "en": "I have been learning English for two years.", "level": "A2"}, {"fr": "Pouvez-vous me montrer le chemin vers la gare ?", "en": "Could you show me the way to the train station?", "level": "A2"}, {"fr": "Nous avons visité Paris l'été dernier.", "en": "We visited Paris last summer.", "level": "A2"}, {"fr": "Cette décision pourrait avoir des conséquences majeures.", "en": "This decision could have major consequences.", "level": "B1"}, {"fr": "La technologie transforme notre façon de communiquer.", "en": "Technology is transforming the way we communicate.", "level": "B1"}, {"fr": "Il est important de protéger l'environnement.", "en": "It is important to protect the environment.", "level": "B1"}, {"fr": "Les recherches scientifiques ont démontré l'efficacité de cette méthode.", "en": "Scientific research has demonstrated the effectiveness of this method.", "level": "B2"}, {"fr": "Les énergies renouvelables représentent l'avenir de notre planète.", "en": "Renewable energies represent the future of our planet.", "level": "B2"}, {"fr": "Il est impératif que nous prenions des mesures immédiates.", "en": "It is imperative that we take immediate action.", "level": "C1"}, {"fr": "La mondialisation a engendré des transformations socio-économiques sans précédent.", "en": "Globalization has brought about unprecedented socio-economic transformations.", "level": "C2"}, ], "error_correction": [ {"wrong": "She don't like to go at school every days.", "correct": "She doesn't like to go to school every day.", "level": "A2", "error_type": "Grammar + Preposition + Plural"}, {"wrong": "He have been work here since three years.", "correct": "He has been working here for three years.", "level": "B1", "error_type": "Grammar (Present Perfect Continuous) + Preposition"}, {"wrong": "She is more smarter than her sister.", "correct": "She is smarter than her sister.", "level": "B1", "error_type": "Grammar (Double comparative)"}, {"wrong": "The goverment anounced a new enviromental policy.", "correct": "The government announced a new environmental policy.", "level": "B2", "error_type": "Spelling"}, {"wrong": "If I would have more time, I will study more.", "correct": "If I had more time, I would study more.", "level": "B2", "error_type": "Conjugation (Conditional type 2)"}, {"wrong": "Despite of his efforts, he don't succeed to pass the exam.", "correct": "Despite his efforts, he did not succeed in passing the exam.", "level": "C1", "error_type": "Grammar + Preposition + Conjugation"}, ], "fill_blank": [ {"display": "She _____ (study) English for five years before she moved to London.", "hint": "Past Perfect", "answer": "had studied", "level": "B1", "error_type": "Conjugation — Past Perfect"}, {"display": "The new policy will have a significant _____ on the economy. (affect / effect)", "hint": "Noun form required", "answer": "effect", "level": "B2", "error_type": "Vocabulary — affect vs. effect"}, {"display": "She has been interested _____ linguistics since her undergraduate studies.", "hint": "Preposition after 'interested'", "answer": "in", "level": "B1", "error_type": "Prepositional usage"}, {"display": "_____ research published in Nature suggests a breakthrough in cancer treatment.", "hint": "Definite article — specific reference", "answer": "The", "level": "C1", "error_type": "Article usage — definite"}, {"display": "By the time she arrives, we _____ (finish) the project.", "hint": "Future Perfect", "answer": "will have finished", "level": "B2", "error_type": "Conjugation — Future Perfect"}, ], "vocabulary": [ {"fr_def": "Personne qui travaille avec vous dans la même organisation\n/ A person who works with you in the same organisation", "answer": "colleague", "synonyms": "co-worker, associate, teammate", "level": "A2", "error_type": "Professional vocabulary"}, {"fr_def": "Rendre quelque chose plus simple à comprendre ou à réaliser\n/ To make something easier to understand or accomplish", "answer": "simplify", "synonyms": "streamline, facilitate, clarify", "level": "B1", "error_type": "Action verb — register"}, {"fr_def": "Qui ne peut pas être évité ou empêché\n/ That which cannot be avoided or prevented", "answer": "inevitable", "synonyms": "unavoidable, inescapable, certain", "level": "B2", "error_type": "Academic adjective"}, {"fr_def": "Ensemble des règles juridiques régissant une société\n/ The body of laws governing a society", "answer": "legislation", "synonyms": "law, regulation, statute, ordinance", "level": "B2", "error_type": "Legal and academic vocabulary"}, {"fr_def": "Étude scientifique du langage et de sa structure\n/ The scientific study of language and its structure", "answer": "linguistics", "synonyms": "language study, philology", "level": "C1", "error_type": "Academic discipline — terminology"}, ], "comprehension": [ { "passage": "Artificial intelligence (AI) has rapidly transformed numerous sectors, including healthcare, education, and transportation. Machine learning algorithms can now diagnose diseases with accuracy comparable to experienced physicians. However, ethical concerns about data privacy and algorithmic bias remain significant challenges that must be addressed by policymakers and researchers alike.", "question": "According to the passage, what two ethical concerns are identified as significant challenges?\n/ Selon le texte, quelles sont les deux préoccupations éthiques identifiées comme défis majeurs ?", "answer": "data privacy and algorithmic bias", "level": "B2", "error_type": "Reading comprehension — information retrieval" }, { "passage": "The Industrial Revolution, which began in Britain in the late 18th century, fundamentally altered the nature of work and society. The shift from agrarian economies to industrial ones displaced millions of workers while simultaneously creating new forms of employment. This period of transition was marked by both unprecedented economic growth and severe social inequalities.", "question": "What two contrasting outcomes of the Industrial Revolution does the author describe?\n/ Quels deux résultats contrastés de la Révolution industrielle l'auteur décrit-il ?", "answer": "unprecedented economic growth and severe social inequalities", "level": "C1", "error_type": "Reading comprehension — synthesis and contrast" }, ] } store = {"ref": "", "display": "", "type": "", "level": "B1", "error_type": "", "pct": 0, "g_kan": 0, "g_lin": 0, "cos": [0,0,0]} def compute_similarity(s1, s2): cos = [] for enc in encoders.values(): e1 = enc.encode([s1], convert_to_numpy=True) e2 = enc.encode([s2], convert_to_numpy=True) e1 /= np.maximum(np.linalg.norm(e1,axis=1,keepdims=True),1e-8) e2 /= np.maximum(np.linalg.norm(e2,axis=1,keepdims=True),1e-8) cos.append(float((e1*e2).sum())) c1,c2,c3 = cos feat = np.array([[c1,c2,c3,abs(c1-c2),abs(c1-c3),abs(c2-c3),(c1+c2+c3)/3,max(cos),min(cos)]],dtype=np.float32) feat_t = torch.tensor(feat) with torch.no_grad(): score = torch.sigmoid(tinykan(feat_t)).item() gates = tinykan.get_gates(feat_t) return round(score*100,1), round(float(gates[0])*100,1), round(float(gates[1])*100,1), [round(c*100,1) for c in cos] def analyze_errors(user_ans, reference): import re def clean(s): return set(re.sub(r'[.,!?;:\-]', '', s.lower()).split()) u = clean(user_ans); r = clean(reference) return r - u, u - r, u & r def get_gemini_feedback(display, user_ans, reference, pct, level, ex_type, error_type): if not gemini_model: return "" try: prompt = f"""You are an academic English language instructor assessing a {level}-level learner. Exercise type: {ex_type} | Linguistic focus: {error_type} Task: {display} Learner response: "{user_ans}" Reference answer: "{reference}" TinyKAN semantic similarity score: {pct}% Provide concise structured bilingual feedback: **English Assessment:** - Accuracy: [correct/incorrect elements] - Errors identified: [grammar / spelling / vocabulary / structure] - Linguistic rule: [relevant rule explained briefly] - Recommendation: [concrete improvement strategy] **Évaluation en français :** - Précision: [éléments corrects/incorrects] - Erreurs: [grammaire / orthographe / vocabulaire / structure] - Règle linguistique: [règle pertinente expliquée brièvement] - Recommandation: [stratégie d'amélioration concrète]""" r = gemini_model.generate_content(prompt) return r.text except: return "" def new_exercise(level, exercise_type, name): pool = [e for e in EXERCISES.get(exercise_type, EXERCISES["translation"]) if e.get("level") == level] if not pool: pool = EXERCISES.get(exercise_type, EXERCISES["translation"]) ex = random.choice(pool) store["level"] = level; store["type"] = exercise_type cfg = { "translation": ("#eff6ff","#1d4ed8","#bfdbfe","🔤 Translation (FR → EN)"), "error_correction": ("#fff7ed","#b45309","#fde68a","✏️ Error Correction"), "fill_blank": ("#f0fdf4","#15803d","#bbf7d0","📝 Fill in the Blank"), "vocabulary": ("#faf5ff","#6d28d9","#ddd6fe","📖 Vocabulary"), "comprehension": ("#fff1f2","#be123c","#fecdd3","📄 Reading Comprehension"), } bg,accent,border,label = cfg.get(exercise_type, ("#f8fafc","#1d4ed8","#e2e8f0","Exercise")) if exercise_type == "translation": store["ref"] = ex["en"]; store["display"] = ex["fr"]; store["error_type"] = "Semantic similarity — FR to EN translation" body = f"""

Translate the following sentence into English. / Traduisez la phrase suivante en anglais.

{ex['fr']}
""" elif exercise_type == "error_correction": store["ref"] = ex["correct"]; store["display"] = ex["wrong"]; store["error_type"] = ex["error_type"] body = f"""

Rewrite the sentence correctly, correcting all linguistic errors. / Réécrivez la phrase correctement en corrigeant toutes les erreurs linguistiques.

“{ex['wrong']}”
""" elif exercise_type == "fill_blank": store["ref"] = ex["answer"]; store["display"] = ex["display"]; store["error_type"] = ex["error_type"] body = f"""

Complete the sentence with the appropriate word or verb form. / Complétez la phrase avec le mot ou la forme verbale appropriée.

{ex['display']}

Grammatical hint / Indice grammatical : {ex['hint']}

""" elif exercise_type == "vocabulary": store["ref"] = ex["answer"]; store["display"] = ex["fr_def"]; store["error_type"] = ex["error_type"] body = f"""

Identify the English term corresponding to the definition below. / Identifiez le terme anglais correspondant à la définition ci-dessous.

{ex['fr_def']}

Related terms / Termes associés : {ex['synonyms']}

""" else: store["ref"] = ex["answer"]; store["display"] = ex["passage"]; store["error_type"] = ex["error_type"] body = f"""

Read the following passage carefully and answer the question below. / Lisez attentivement le texte suivant et répondez à la question ci-dessous.

{ex['passage']}
{ex['question']}
""" task_html = f"""
{label} — CEFR Level {level}
{body}
""" return task_html, "", gr.update(visible=False), gr.update(visible=False) def evaluate(user_answer): if not user_answer.strip() or not store["ref"]: return gr.update(visible=False), gr.update(visible=False) pct, g_kan, g_lin, cos_scores = compute_similarity(user_answer.strip(), store["ref"]) store["pct"]=pct; store["g_kan"]=g_kan; store["g_lin"]=g_lin; store["cos"]=cos_scores missing, extra, correct = analyze_errors(user_answer, store["ref"]) if pct >= 85: emoji,label = "🏆","Excellent" color,bg,border = "#166534","#f0fdf4","#bbf7d0" msg_en = "The response demonstrates high semantic accuracy and strong linguistic command." msg_fr = "La réponse témoigne d'une grande précision sémantique et d'une solide maîtrise linguistique." elif pct >= 70: emoji,label = "✅","Satisfactory / Satisfaisant" color,bg,border = "#1e40af","#eff6ff","#bfdbfe" msg_en = "The response is semantically adequate with minor lexical or structural deviations." msg_fr = "La réponse est sémantiquement adéquate avec des écarts lexicaux ou structurels mineurs." elif pct >= 50: emoji,label = "⚠️","Partially Correct / Partiellement correct" color,bg,border = "#92400e","#fffbeb","#fde68a" msg_en = "The response conveys the general idea but requires significant linguistic revision." msg_fr = "La réponse transmet l'idée générale mais nécessite une révision linguistique significative." else: emoji,label = "❌","Insufficient / Insuffisant" color,bg,border = "#991b1b","#fef2f2","#fecaca" msg_en = "The response deviates substantially from the reference. A thorough revision is required." msg_fr = "La réponse s'écarte substantiellement de la référence. Une révision approfondie est nécessaire." result_html = f"""
Section I — Semantic Similarity Score / Score de Similarité Sémantique
{emoji}
{label}
Computed by TinyKAN-Distill — Kolmogorov–Arnold Network (32,080 parameters)
{pct}%
similarity
ENGLISH ASSESSMENT
{msg_en}
ÉVALUATION
{msg_fr}
Section II — Answer Comparison & Linguistic Analysis / Comparaison et Analyse Linguistique
LEARNER RESPONSE / RÉPONSE
{user_answer}
REFERENCE ANSWER / RÉPONSE DE RÉFÉRENCE
{store['ref']}
Category / Catégorie Words / Mots N
✅ Correct vocabulary
Vocabulaire correct
{', '.join(sorted(correct)[:12]) if correct else '—'} {len(correct)}
❌ Missing words
Mots manquants
{', '.join(sorted(missing)[:12]) if missing else '—'} {len(missing)}
⚠️ Extraneous words
Mots en trop
{', '.join(sorted(extra)[:12]) if extra else '—'} {len(extra)}
🎯 Linguistic category
Catégorie d'erreur
{store['error_type']}
""" gemini_fb = get_gemini_feedback( store["display"], user_answer, store["ref"], pct, store["level"], store["type"], store["error_type"] ) gemini_html = "" if gemini_fb: gemini_html = f"""
Section III — AI Pedagogical Analysis / Analyse Pédagogique par Intelligence Artificielle
Generated by Google Gemini 1.5 Flash based on the semantic score computed by TinyKAN-Distill ({pct}%).
Généré par Google Gemini 1.5 Flash sur la base du score calculé par TinyKAN-Distill ({pct}%).
{gemini_fb}
""" pipeline_html = f"""
Last Computation Pipeline / Dernier Pipeline de Calcul
MiniLM-L6-v2 (22M)
{cos_scores[0]}%
cosine similarity
MPNet-base-v2 (110M)
{cos_scores[1]}%
cosine similarity
RoBERTa-STS (125M)
{cos_scores[2]}%
cosine similarity
TinyKAN fusion score: {pct}% KAN spline branch: {g_kan}%  |  Linear bypass: {g_lin}%
""" return gr.update(visible=True), gr.update(visible=True), result_html, gemini_html, pipeline_html # ── CORRECTION ICI : 7 → 9 datasets + ajout SICK-R et STS22 ── TECH_HTML = """
Multi-Dataset Evaluation — TinyKAN-Distill (Pearson r / Spearman ρ)
Evaluated on 9 STS benchmarks using a 9-dimensional cosine-similarity pipeline. Évalué sur 9 benchmarks STS avec un pipeline cosine à 9 dimensions.
Dataset Pearson r ↑ 95% CI Spearman ρ ↑ N pairs
⭐ STS-Benchmark (primary) 0.8791 [0.8662, 0.8909] 0.8752 1,379
STS-20120.8813[0.8699, 0.8911]0.79493,108
STS-20130.9046[0.8949, 0.9149]0.89361,500
STS-20140.9050[0.8984, 0.9116]0.88153,750
STS-2015 🏆0.9137[0.9063, 0.9207]0.91583,000
STS-20160.8541[0.8386, 0.8690]0.85821,186
STS-2017 (en-en)0.8917[0.8600, 0.9164]0.8837250
SICK-R0.8315[0.8244, 0.8381]0.80669,927
STS22 (en) harder benchmark0.6706[0.5675, 0.7638]0.6557197
TINYKAN ARCHITECTURE
Model typeHybrid KAN (spline + linear + gate)
Parameters32,080
Input features9 (cosine similarity)
KAN width[9 → 16 → 8 → 1]
Compression ratio8,012× vs. teacher ensemble
Distillationα = 0.7 | T = 3.0
TEACHER ENSEMBLE
Teacher 1all-MiniLM-L6-v2 (22M)
Teacher 2all-mpnet-base-v2 (110M)
Teacher 3stsb-roberta-base (125M)
Total capacity257M parameters
WeightingInverse-variance
Ensemble Pearson r0.9146
""" with gr.Blocks(title="English Learning Assistant — TinyKAN-Distill", theme=gr.themes.Soft()) as demo: gr.HTML("""

🎓 English Learning Assistant

Intelligent language assessment via Ensemble Knowledge Distillation into Kolmogorov–Arnold Networks (TinyKAN-Distill)

Developed by Mouna Khlifi · University of Kairouan, Tunisia · 2026

🧠 TinyKAN-Distill 🤖 Gemini 1.5 Flash 📚 CEFR A1–C2 🌍 EN / FR
""") with gr.Row(): with gr.Column(scale=1, min_width=250): gr.HTML('
Learner Profile / Profil de l\'Apprenant
') learner_name = gr.Textbox(label="Full name / Nom complet", placeholder="e.g. Mouna Khlifi") level = gr.Dropdown( ["A1 — Beginner / Débutant","A2 — Elementary / Élémentaire", "B1 — Intermediate / Intermédiaire","B2 — Upper-Intermediate / Intermédiaire supérieur", "C1 — Advanced / Avancé","C2 — Proficient / Maîtrise"], value="B1 — Intermediate / Intermédiaire", label="CEFR Proficiency Level / Niveau de compétence CEFR") exercise_type = gr.Dropdown( choices=[ ("🔤 FR→EN Translation / Traduction français vers anglais", "translation"), ("✏️ Error Correction / Correction d'erreurs linguistiques", "error_correction"), ("📝 Fill in the Blank / Complétez la phrase", "fill_blank"), ("📖 Vocabulary — Term Identification / Identification de termes", "vocabulary"), ("📄 Reading Comprehension / Compréhension de l'écrit", "comprehension"), ], value="translation", label="Exercise Type / Type d'exercice") new_btn = gr.Button("▶ Start Exercise / Commencer l'exercice", variant="primary", size="lg") gr.HTML("""
Assessment Scale / Barème
🏆 ≥ 85% — Excellent
✅ ≥ 70% — Satisfactory / Satisfaisant
⚠️ ≥ 50% — Partially correct / Partiellement correct
❌ < 50% — Insufficient / Insuffisant

AI Scoring System
Similarity score: TinyKAN-Distill
Pedagogical feedback: Gemini 1.5 Flash
""") with gr.Column(scale=2): gr.HTML('
Exercise / Exercice
') exercise_html = gr.HTML("""
Select your proficiency level and exercise type,
then click Start Exercise to begin.
Sélectionnez votre niveau et le type d'exercice,
puis cliquez sur Commencer l'exercice.
""") gr.HTML('
Your Answer / Votre Réponse
') answer_box = gr.Textbox( label="Write your answer below / Rédigez votre réponse ci-dessous", placeholder="Enter your answer here / Saisissez votre réponse ici...", lines=3) with gr.Row(): submit_btn = gr.Button("✅ Submit Answer / Soumettre la réponse", variant="primary", scale=2) next_btn = gr.Button("⏭ Next / Suivant", variant="secondary", scale=1, visible=False) result_col = gr.Column(visible=False) with result_col: gr.HTML('
Assessment Results / Résultats d\'Évaluation
') result_html = gr.HTML("") gemini_col = gr.Column(visible=False) with gemini_col: gemini_html = gr.HTML("") with gr.Accordion("📊 Technical Details & Multi-Dataset Evaluation / Détails Techniques & Évaluation Multi-Dataset", open=False): gr.HTML(TECH_HTML) pipeline_html = gr.HTML("") gr.HTML("""
© 2026 Mouna Khlifi — University of Kairouan, Tunisia  ·  TinyKAN-Distill: Ensemble Knowledge Distillation into Kolmogorov–Arnold Networks  ·  GitHub Repository →
""") def parse_level(lv): return lv.split(" — ")[0].strip() if " — " in lv else lv.strip() new_btn.click( lambda lv, et, nm: new_exercise(parse_level(lv), et, nm), inputs=[level, exercise_type, learner_name], outputs=[exercise_html, answer_box, result_col, gemini_col]) submit_btn.click( evaluate, inputs=[answer_box], outputs=[result_col, gemini_col, result_html, gemini_html, pipeline_html]) next_btn.click( lambda lv, et, nm: new_exercise(parse_level(lv), et, nm), inputs=[level, exercise_type, learner_name], outputs=[exercise_html, answer_box, result_col, gemini_col]) demo.launch()