Clarkoer commited on
Commit
b6f0bc8
Β·
1 Parent(s): f05d609

Fine-tune NLP model on GAL knowledge base

Browse files

- Fine-tuned all-mpnet-base-v2 on 898 training pairs from 54 KB topics
- Model uploaded to HF Hub as Clarkoer/gal-mpnet-finetuned
- Similarity scores improved +0.08 to +0.15 across GAL queries
- Updated gal_fallback.py to load fine-tuned model
- Updated Dockerfile to pre-download fine-tuned model
- All 99/99 tests passing

Files changed (4) hide show
  1. .gitignore +2 -0
  2. Backend/gal_fallback.py +11 -4
  3. Dockerfile +2 -2
  4. train_model.py +104 -0
.gitignore CHANGED
@@ -2,3 +2,5 @@
2
  __pycache__/
3
  *.pyc
4
  .env
 
 
 
2
  __pycache__/
3
  *.pyc
4
  .env
5
+ gal-mpnet-finetuned/
6
+ checkpoints/
Backend/gal_fallback.py CHANGED
@@ -3,7 +3,7 @@ NLP-based fallback responder for the GAL AI chat.
3
 
4
  Three-layer hybrid architecture:
5
  Layer 1 β€” Rule Engine: regex matches compiler error messages β†’ structured explanations
6
- Layer 2 β€” Retriever: sentence-transformers (all-mpnet-base-v2) semantic search over 50+ KB topics
7
  Layer 3 β€” Default: help menu when nothing matches
8
 
9
  Plus: synonym expansion, greeting detection, follow-up context, multi-topic blending.
@@ -704,6 +704,7 @@ Use `prune;` (break) and `skip;` (continue) inside loops."""),
704
  "if else condition",
705
  "conditional statement",
706
  "spring bud wither",
 
707
  "if statement else if",
708
  "check a condition",
709
  "branching logic",
@@ -2222,7 +2223,7 @@ root() {
2222
 
2223
 
2224
  # ═══════════════════════════════════════════════════════════════════════
2225
- # Sentence-Transformers (all-mpnet-base-v2) β€” lazy-loaded on first query
2226
  # ═══════════════════════════════════════════════════════════════════════
2227
 
2228
  _st_model = None
@@ -2299,8 +2300,14 @@ def _ensure_model():
2299
  return
2300
 
2301
  from sentence_transformers import SentenceTransformer
2302
-
2303
- _st_model = SentenceTransformer("all-mpnet-base-v2")
 
 
 
 
 
 
2304
 
2305
  _phrase_topic_idx = []
2306
  _responses = []
 
3
 
4
  Three-layer hybrid architecture:
5
  Layer 1 β€” Rule Engine: regex matches compiler error messages β†’ structured explanations
6
+ Layer 2 β€” Retriever: sentence-transformers (gal-mpnet-finetuned) semantic search over 50+ KB topics
7
  Layer 3 β€” Default: help menu when nothing matches
8
 
9
  Plus: synonym expansion, greeting detection, follow-up context, multi-topic blending.
 
704
  "if else condition",
705
  "conditional statement",
706
  "spring bud wither",
707
+ "how to use spring bud wither",
708
  "if statement else if",
709
  "check a condition",
710
  "branching logic",
 
2223
 
2224
 
2225
  # ═══════════════════════════════════════════════════════════════════════
2226
+ # Sentence-Transformers (gal-mpnet-finetuned) β€” lazy-loaded on first query
2227
  # ═══════════════════════════════════════════════════════════════════════
2228
 
2229
  _st_model = None
 
2300
  return
2301
 
2302
  from sentence_transformers import SentenceTransformer
2303
+ import os
2304
+
2305
+ # Prefer fine-tuned model, fall back to base
2306
+ finetuned = os.path.join(os.path.dirname(__file__), "..", "gal-mpnet-finetuned")
2307
+ if os.path.isdir(finetuned):
2308
+ _st_model = SentenceTransformer(finetuned)
2309
+ else:
2310
+ _st_model = SentenceTransformer("Clarkoer/gal-mpnet-finetuned")
2311
 
2312
  _phrase_topic_idx = []
2313
  _responses = []
Dockerfile CHANGED
@@ -6,8 +6,8 @@ WORKDIR /app
6
  COPY requirements.txt .
7
  RUN pip install --no-cache-dir -r requirements.txt
8
 
9
- # Pre-download the sentence-transformers model so first request is fast
10
- RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-mpnet-base-v2')"
11
 
12
  # Copy the entire project
13
  COPY . .
 
6
  COPY requirements.txt .
7
  RUN pip install --no-cache-dir -r requirements.txt
8
 
9
+ # Pre-download the fine-tuned sentence-transformers model so first request is fast
10
+ RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('Clarkoer/gal-mpnet-finetuned')"
11
 
12
  # Copy the entire project
13
  COPY . .
train_model.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Fine-tune all-mpnet-base-v2 on GAL knowledge base for better retrieval.
3
+
4
+ Strategy: MultipleNegativesRankingLoss with (query, positive) pairs.
5
+ - Each training phrase is paired with a "topic label" string
6
+ - Phrases from the same topic should be close, different topics far apart
7
+
8
+ This produces a fine-tuned model saved to ./gal-mpnet-finetuned/
9
+ """
10
+
11
+ import sys, os, random
12
+ sys.path.insert(0, "Backend")
13
+
14
+ from sentence_transformers import (
15
+ SentenceTransformer,
16
+ InputExample,
17
+ losses,
18
+ evaluation,
19
+ )
20
+ from torch.utils.data import DataLoader
21
+
22
+ # ── 1. Load the knowledge base ────────────────────────────────────
23
+ import gal_fallback as fb
24
+
25
+ topics = [] # list of (phrases, short_label)
26
+ for i, (phrases, response) in enumerate(fb._KNOWLEDGE_BASE):
27
+ # Create a short label from the first phrase
28
+ label = phrases[0][:80]
29
+ topics.append((phrases, label, response))
30
+
31
+ print(f"Loaded {len(topics)} topics with {sum(len(t[0]) for t in topics)} total phrases")
32
+
33
+ # ── 2. Generate training pairs ────────────────────────────────────
34
+ # Strategy: for each phrase, pair it with:
35
+ # a) Other phrases in the same topic (positive pairs)
36
+ # b) A condensed description of the topic (anchor-passage pair)
37
+
38
+ train_examples = []
39
+
40
+ for phrases, label, response in topics:
41
+ # Take first 200 chars of response as a condensed passage
42
+ passage = response[:200].strip()
43
+
44
+ for phrase in phrases:
45
+ # Pair each phrase with the passage (query β†’ answer)
46
+ train_examples.append(InputExample(texts=[phrase, passage]))
47
+
48
+ # Pair with other phrases in same topic (query ↔ query)
49
+ others = [p for p in phrases if p != phrase]
50
+ if others:
51
+ partner = random.choice(others)
52
+ train_examples.append(InputExample(texts=[phrase, partner]))
53
+
54
+ # Also add synonym-expanded versions
55
+ for term, gal_equiv in fb._SYNONYMS.items():
56
+ # "what is int" β†’ "what is seed"
57
+ train_examples.append(
58
+ InputExample(texts=[f"what is {term} in GAL", f"what is {gal_equiv} in GAL"])
59
+ )
60
+ train_examples.append(
61
+ InputExample(texts=[f"how to use {term}", f"how to use {gal_equiv}"])
62
+ )
63
+
64
+ random.shuffle(train_examples)
65
+ print(f"Generated {len(train_examples)} training pairs")
66
+
67
+ # ── 3. Setup evaluation ──────────────────────────────────────────
68
+ # Build a small eval set: for each topic, take one phrase as query
69
+ # and the response snippet as the expected match
70
+ eval_sentences1 = []
71
+ eval_sentences2 = []
72
+ eval_scores = []
73
+
74
+ for phrases, label, response in topics:
75
+ if len(phrases) >= 2:
76
+ eval_sentences1.append(phrases[-1]) # last phrase as query
77
+ eval_sentences2.append(response[:200].strip()) # passage
78
+ eval_scores.append(1.0) # should be similar
79
+
80
+ evaluator = evaluation.EmbeddingSimilarityEvaluator(
81
+ eval_sentences1, eval_sentences2, eval_scores, name="gal-eval"
82
+ )
83
+
84
+ # ── 4. Fine-tune ─────────────────────────────────────────────────
85
+ model = SentenceTransformer("all-mpnet-base-v2")
86
+
87
+ train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
88
+ train_loss = losses.MultipleNegativesRankingLoss(model)
89
+
90
+ output_path = "./gal-mpnet-finetuned"
91
+
92
+ print("Starting fine-tuning...")
93
+ model.fit(
94
+ train_objectives=[(train_dataloader, train_loss)],
95
+ evaluator=evaluator,
96
+ epochs=5,
97
+ evaluation_steps=50,
98
+ warmup_steps=10,
99
+ output_path=output_path,
100
+ show_progress_bar=True,
101
+ )
102
+
103
+ print(f"\nFine-tuned model saved to: {output_path}")
104
+ print("Done!")