Дообучение Embedding-модели под домен заказчика
Стандартные embedding-модели обучены на общем веб-тексте и академических корпусах. В специализированных доменах (медицина, право, финансы, технические стандарты) они плохо различают специфическую терминологию и показывают сниженный retrieval recall. Fine-tuning embedding-модели под конкретный домен повышает качество поиска без смены всей RAG-инфраструктуры.
Когда необходимо дообучение embedding-модели
Признаки, указывающие на необходимость fine-tuning:
- Общая embedding-модель плохо различает специфические термины домена (MeSH terms, юридические конструкции, технические аббревиатуры)
- Context recall RAG-системы застрял ниже 0.75 несмотря на оптимизацию чанкинга и поиска
- Высокая доля «ложных срабатываний» — семантически похожие, но тематически нерелевантные документы попадают в top-K
Подготовка обучающих данных: Triplet vs Contrastive
Triplet Loss: якорь (anchor), позитив (релевантный документ), негатив (нерелевантный).
Contrastive / Multi Negative Ranking Loss: пары (запрос, релевантный документ). Негативы берутся из остальных документов в батче.
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
# Обучающие данные: пары (query, relevant_document)
train_examples = [
InputExample(texts=["каков срок исковой давности по трудовым спорам",
"Срок исковой давности по индивидуальным трудовым спорам составляет 3 месяца..."]),
InputExample(texts=["расчёт пособия по временной нетрудоспособности",
"Размер пособия по временной нетрудоспособности определяется..."]),
# ... ещё примеры
]
# Загрузка базовой модели
model = SentenceTransformer("BAAI/bge-m3")
# Multi Negative Ranking Loss (эффективнее triplet для retrieval)
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=32)
train_loss = losses.MultipleNegativesRankingLoss(model)
# Обучение
model.fit(
train_objectives=[(train_dataloader, train_loss)],
epochs=3,
warmup_steps=100,
output_path="./bge-m3-legal-finetuned",
show_progress_bar=True,
)
Генерация обучающих пар с LLM
Для создания обучающего датасета без ручной разметки:
from openai import OpenAI
import json
client = OpenAI()
def generate_queries_for_document(doc_text: str, n: int = 5) -> list[str]:
"""Генерирует поисковые запросы, на которые отвечает документ"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": f"""Сгенерируй {n} поисковых запросов, которые пользователь мог бы задать,
чтобы найти следующий документ. Запросы должны быть разными по формулировке,
но все должны быть отвечены этим документом.
Верни JSON-список строк.
Документ:
{doc_text[:1000]}"""
}],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)["queries"]
# Генерация датасета из 1000 документов
training_pairs = []
for doc in domain_documents:
queries = generate_queries_for_document(doc.text)
for query in queries:
training_pairs.append(InputExample(texts=[query, doc.text]))
print(f"Сгенерировано {len(training_pairs)} обучающих пар")
# Типично: 1000 документов × 5 запросов = 5000 пар за ~2 часа и $5-15
Оценка улучшения retrieval
from sentence_transformers.evaluation import InformationRetrievalEvaluator
# Тестовый набор: {query_id: query, corpus_id: doc, relevant_docs: {query_id: [doc_ids]}}
evaluator = InformationRetrievalEvaluator(
queries=test_queries,
corpus=test_corpus,
relevant_docs=relevance_labels,
precision_recall_at_k=[1, 5, 10],
ndcg_at_k=[10],
show_progress_bar=True,
)
# Сравнение базовой и дообученной модели
base_model = SentenceTransformer("BAAI/bge-m3")
finetuned_model = SentenceTransformer("./bge-m3-legal-finetuned")
base_results = evaluator(base_model, output_path="./eval_base")
ft_results = evaluator(finetuned_model, output_path="./eval_finetuned")
Практический кейс: юридические документы
Базовая модель: BAAI/bge-m3.
Датасет: 8000 пар (синтетических: 6500 через GPT-4o-mini + 1500 ручных).
Домен: российское трудовое и гражданское право.
| Метрика | До FT | После FT |
|---|---|---|
| NDCG@10 | 0.68 | 0.84 |
| Recall@5 | 0.61 | 0.79 |
| MRR@5 | 0.65 | 0.82 |
| Latency (inference) | без изменений | без изменений |
+24% к NDCG без изменения инфраструктуры — только обновление весов модели.
Сроки
- Генерация обучающего датасета: 3–7 дней
- Fine-tuning (bge-m3, 5000 пар): 2–4 часа (A100)
- Evaluation и сравнение: 2–3 дня
- Итого: 1–2 недели







