Spaces:
Sleeping
Sleeping
| # src/semantic_features.py | |
| from __future__ import annotations | |
| from functools import lru_cache | |
| import numpy as np | |
| import pandas as pd | |
| from sentence_transformers import SentenceTransformer | |
| from src.semantic_cache import embed_with_cache | |
| _MODEL_NAME = "ai-forever/sbert_large_nlu_ru" | |
| def _load_model() -> SentenceTransformer: | |
| """Ленивая загрузка модели (кэшируется в процессе).""" | |
| return SentenceTransformer(_MODEL_NAME) | |
| def add_semantic_similarity( | |
| df: pd.DataFrame, | |
| batch_size: int = 16, # меньше батч по умолчанию, чтобы точно писался кэш | |
| verbose: bool = True, | |
| ) -> pd.DataFrame: | |
| """ | |
| Добавляет колонку 'semantic_sim' — косинусное сходство вопроса и ответа. | |
| Эмбеддинги извлекаются через embed_with_cache (нормализованы), | |
| поэтому cos_sim == dot(a, q). | |
| """ | |
| out = df.copy() | |
| # гарантируем наличие столбцов | |
| for col in ("question_text", "answer_text"): | |
| if col not in out.columns: | |
| out[col] = "" | |
| if len(out) == 0: | |
| out["semantic_sim"] = np.array([], dtype=np.float32) | |
| return out | |
| model = _load_model() | |
| q_texts = out["question_text"].fillna("").astype(str).tolist() | |
| a_texts = out["answer_text"].fillna("").astype(str).tolist() | |
| if verbose: | |
| print("🔹 Проверяем кэш (вопросы)...") | |
| q_emb = embed_with_cache(q_texts, model, batch_size=batch_size, verbose=verbose) # (N, D) float32 | |
| if verbose: | |
| print("🔹 Проверяем кэш (ответы)...") | |
| a_emb = embed_with_cache(a_texts, model, batch_size=batch_size, verbose=verbose) # (N, D) float32 | |
| # косинус = скалярное произведение (векторы уже нормированы в embed_with_cache) | |
| sims = (a_emb * q_emb).sum(axis=1).astype(np.float32) | |
| np.clip(sims, -1.0, 1.0, out=sims) | |
| out["semantic_sim"] = sims | |
| return out | |