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
- .gitignore +2 -0
- Backend/gal_fallback.py +11 -4
- Dockerfile +2 -2
- 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 (
|
| 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 (
|
| 2226 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2227 |
|
| 2228 |
_st_model = None
|
|
@@ -2299,8 +2300,14 @@ def _ensure_model():
|
|
| 2299 |
return
|
| 2300 |
|
| 2301 |
from sentence_transformers import SentenceTransformer
|
| 2302 |
-
|
| 2303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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('
|
| 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!")
|