exam-evaluator / src /semantic_features.py
KarmanovaLidiia
Initial clean commit for HF Space (models via Git LFS)
bcb314a
# 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"
@lru_cache(maxsize=1)
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