From 83b21166169eb02737d65c7bde2a7e0f6e3c0d60 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 28 Jan 2026 00:16:57 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC=20=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=87=D0=B5=D1=82=D0=B0=20score:=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BF-k=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D1=81=D1=80=D0=B5=D0=B4=D0=BD=D0=B5=D0=B3=D0=BE=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=D0=B9=20=D1=87?= =?UTF-8?q?=D1=83=D0=B2=D1=81=D1=82=D0=B2=D0=B8=D1=82=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/storage/vector_store.py | 66 ++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/app/storage/vector_store.py b/app/storage/vector_store.py index a680502..ac74bbc 100644 --- a/app/storage/vector_store.py +++ b/app/storage/vector_store.py @@ -244,9 +244,9 @@ class VectorStore: Рассчитывает скор на основе сходства с примерами. Алгоритм: - 1. Вычисляем среднее косинусное сходство с положительными примерами - 2. Вычисляем среднее косинусное сходство с отрицательными примерами - 3. Финальный скор = pos_sim / (pos_sim + neg_sim + eps) + 1. Вычисляем косинусное сходство со всеми примерами + 2. Используем топ-k ближайших примеров для более чувствительной оценки + 3. Сравниваем топ-k положительных с топ-k отрицательными Args: vector: Векторное представление нового поста @@ -275,28 +275,47 @@ class VectorStore: # Косинусное сходство с положительными примерами # Для нормализованных векторов это просто скалярное произведение pos_similarities = np.dot(pos_matrix, normalized) - pos_sim = float(np.mean(pos_similarities)) # Косинусное сходство с отрицательными примерами if self.negative_count > 0: neg_matrix = np.array(self._negative_vectors) neg_similarities = np.dot(neg_matrix, normalized) - neg_sim = float(np.mean(neg_similarities)) + else: + neg_similarities = np.array([]) + + # Используем топ-k ближайших примеров для более чувствительной оценки + # k выбирается динамически: минимум 10, но не больше 20% от общего количества + k_pos = min(max(10, self.positive_count // 10), 50) + k_pos = min(k_pos, len(pos_similarities)) + + # Топ-k положительных примеров + top_k_pos_sim = float(np.mean(np.sort(pos_similarities)[-k_pos:])) + + # Для отрицательных: если их меньше k, берем все, иначе топ-k + if len(neg_similarities) > 0: + k_neg = min(max(10, self.negative_count // 10), 50) + k_neg = min(k_neg, len(neg_similarities)) + top_k_neg_sim = float(np.mean(np.sort(neg_similarities)[-k_neg:])) else: # Если нет отрицательных примеров, используем нейтральное значение - neg_sim = pos_sim # Нейтральный скор = 0.5 + top_k_neg_sim = top_k_pos_sim # Нейтральный скор = 0.5 - # === Вариант 1: neg/pos (разница между положительными и отрицательными) === - diff = pos_sim - neg_sim - score_neg_pos = 0.5 + (diff * self.score_multiplier) + # === Вариант 1: neg/pos (разница между топ-k положительными и отрицательными) === + # Используем более агрессивную нормализацию для малых различий + diff = top_k_pos_sim - top_k_neg_sim + + # Адаптивный множитель: чем больше примеров, тем выше чувствительность + adaptive_multiplier = self.score_multiplier * (1.0 + min(1.0, (self.positive_count + self.negative_count) / 1000)) + + score_neg_pos = 0.5 + (diff * adaptive_multiplier) score_neg_pos = max(0.0, min(1.0, score_neg_pos)) # === Вариант 2: pos only (только положительные, топ-k ближайших) === # Берём топ-5 ближайших положительных примеров - top_k = min(5, len(pos_similarities)) - top_k_sim = float(np.mean(np.sort(pos_similarities)[-top_k:])) + top_5_k = min(5, len(pos_similarities)) + top_5_sim = float(np.mean(np.sort(pos_similarities)[-top_5_k:])) # Нормализуем: 0.85 -> 0.0, 0.95 -> 1.0 (типичный диапазон для BERT) - score_pos_only = (top_k_sim - 0.85) / 0.10 + score_pos_only = (top_5_sim - 0.85) / 0.10 score_pos_only = max(0.0, min(1.0, score_pos_only)) # Основной скор — neg/pos @@ -306,10 +325,27 @@ class VectorStore: total_examples = self.positive_count + self.negative_count confidence = min(1.0, total_examples / 1000) + # Дополнительная диагностическая информация + pos_mean = float(np.mean(pos_similarities)) + pos_std = float(np.std(pos_similarities)) + pos_min = float(np.min(pos_similarities)) + pos_max = float(np.max(pos_similarities)) + + if len(neg_similarities) > 0: + neg_mean = float(np.mean(neg_similarities)) + neg_std = float(np.std(neg_similarities)) + neg_min = float(np.min(neg_similarities)) + neg_max = float(np.max(neg_similarities)) + else: + neg_mean = neg_std = neg_min = neg_max = 0.0 + logger.info( - f"VectorStore: pos_sim={pos_sim:.4f}, neg_sim={neg_sim:.4f}, " - f"top_k_sim={top_k_sim:.4f}, score_neg_pos={score_neg_pos:.4f}, " - f"score_pos_only={score_pos_only:.4f}" + f"VectorStore: top_k_pos={k_pos}, top_k_neg={k_neg if len(neg_similarities) > 0 else 0}, " + f"top_k_pos_sim={top_k_pos_sim:.4f}, top_k_neg_sim={top_k_neg_sim:.4f}, " + f"diff={diff:.4f}, adaptive_mult={adaptive_multiplier:.2f}, " + f"score_neg_pos={score_neg_pos:.4f}, score_pos_only={score_pos_only:.4f}, " + f"pos_mean={pos_mean:.4f}±{pos_std:.4f}[{pos_min:.4f}-{pos_max:.4f}], " + f"neg_mean={neg_mean:.4f}±{neg_std:.4f}[{neg_min:.4f}-{neg_max:.4f}]" ) return score, confidence, score_pos_only