Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Метрики сходства: когда какую использовать
similarity_metrics

Метрики сходства: когда какую использовать

Cosine, Euclidean, Dot Product — разбираем на примерах, какая метрика для какой задачи подходит. Практические тесты и сравнения.

Метрики сходства: когда какую использовать

Cosine, Euclidean, Dot Product — разбираем на примерах, какая метрика для какой задачи подходит. Практические тесты и сравнения.

#Что вы сделаете в этой теме

За 45 минут вы:

  1. Поймёте разницу между Cosine, Euclidean и Dot Product
  2. Научитесь выбирать метрику под задачу
  3. Протестируете метрики на своих данных
  4. Научитесь интерпретировать результаты

#Зачем нужны разные метрики

После генерации эмбеддингов следующий шаг — найти ближайшие векторы. Но что значит "ближайшие"?

Разные метрики измеряют разные виды близости:

A = [0.5, 0.3, 0.2] B = [0.48, 0.32, 0.21] # Похож на A C = [1.0, 0.6, 0.4] # То же направление, но в 2 раза длиннее # Cosine Similarity: cosine(A, B) = 0.998 # Очень похожи cosine(A, C) = 1.0 # Идентичное направление! # Euclidean Distance: L2(A, B) = 0.037 # Близко L2(A, C) = 0.54 # Дальше

Вывод: Cosine игнорирует длину вектора, Euclidean учитывает.

#Метрика 1: Cosine Similarity (косинусное сходство)

#Формула

cos(θ) = (A · B) / (‖A‖ × ‖B‖)

Где:

  • A · B — скалярное произведение
  • ‖A‖ — длина (норма) вектора

#Диапазон и интерпретация

ЗначениеИнтерпретация
1.0Идентичное направление (максимальное сходство)
0.7–0.9Очень похожие
0.5–0.7Умеренное сходство
0.3–0.5Слабое сходство
0.0Ортогональные (нет связи)
< 0Противоположное направление

#Когда использовать

✅ Подходит для:

  • Текстовых документов, статей, постов
  • Когда важна ориентация, а не величина
  • Поиска по смыслу (семантический поиск)
  • Рекомендательных систем

❌ Не подходит для:

  • Когда важна абсолютная разница (например, цены)
  • Кластеризации K-means (использует L2)

#Пример кода

import numpy as np from sklearn.metrics.pairwise import cosine_similarity # Два вектора A = np.array([[0.12, -0.45, 0.78, 0.33]]) B = np.array([[0.15, -0.42, 0.75, 0.31]]) C = np.array([[0.89, 0.23, -0.12, 0.67]]) # Косинусное сходство sim_ab = cosine_similarity(A, B)[0][0] # 0.998 sim_ac = cosine_similarity(A, C)[0][0] # 0.123 print(f"A ↔ B: {sim_ab:.3f}") # Очень похожи print(f"A ↔ C: {sim_ac:.3f}") # Не похожи

#Быстрое вычисление через скалярное произведение

Для нормализованных векторов (длина = 1):

from sklearn.preprocessing import normalize # Нормализация A_norm = normalize(A)[0] B_norm = normalize(B)[0] # Скалярное произведение = косинусное сходство similarity = np.dot(A_norm, B_norm)

Почему это работает:

cos(θ) = (A · B) / (‖A‖ × ‖B‖)

Когда ‖A‖ = ‖B‖ = 1 (нормализованы):
cos(θ) = A · B

#Метрика 2: Euclidean Distance (евклидово расстояние, L2)

#Формула

L2(A, B) = √Σ(Ai - Bi)²

#Диапазон и интерпретация

ЗначениеИнтерпретация
0.0Идентичные векторы
0.0–0.5Очень близкие
0.5–1.0Близкие
1.0–2.0Умеренное расстояние
> 2.0Далёкие векторы

Важно: Значения зависят от масштабирования данных!

#Когда использовать

✅ Подходит для:

  • Когда важна абсолютная разница между векторами
  • Кластеризации (K-means использует L2)
  • Данных с нормальной распределённостью
  • Поиска дубликатов (идентичные = расстояние 0)

❌ Не подходит для:

  • Текстов разной длины (длинные документы будут дальше)
  • Когда направление важнее величины

#Пример кода

from scipy.spatial import distance A = np.array([0.12, -0.45, 0.78, 0.33]) B = np.array([0.15, -0.42, 0.75, 0.31]) C = np.array([0.89, 0.23, -0.12, 0.67]) # Евклидово расстояние dist_ab = distance.euclidean(A, B) # 0.057 dist_ac = distance.euclidean(A, C) # 1.42 print(f"Расстояние A ↔ B: {dist_ab:.3f}") # Близко print(f"Расстояние A ↔ C: {dist_ac:.3f}") # Далёко

#Связь с косинусным сходством

Для нормализованных векторов:

# L2² = 2 × (1 - cosine_similarity) # cosine_similarity = 1 - (L2² / 2) cosine = 0.9 l2_squared = 2 * (1 - cosine) # 0.2 l2 = np.sqrt(l2_squared) # 0.447

#Метрика 3: Dot Product (скалярное произведение)

#Формула

A · B = Σ(Ai × Bi)

#Диапазон

От -∞ до +∞ (зависит от масштаба векторов)

Важно: Без нормализации значения трудно интерпретировать!

#Когда использовать

✅ Подходит для:

  • Нормализованных векторов (эквивалентно cosine)
  • Когда скорость критична (быстрее cosine)
  • Поиска в FAISS с IndexFlatIP
  • Рекомендательных систем (коллаборативная фильтрация)

❌ Не подходит для:

  • Ненормализованных векторов (зависит от масштаба)
  • Когда нужна интерпретируемость (-1 до 1)

#Пример кода

A = np.array([0.5, 0.3, 0.2]) B = np.array([0.48, 0.32, 0.21]) # Скалярное произведение dot = np.dot(A, B) # 0.38 # Для нормализованных векторов from sklearn.preprocessing import normalize A_norm = normalize(A.reshape(1, -1))[0] B_norm = normalize(B.reshape(1, -1))[0] dot_normalized = np.dot(A_norm, B_norm) # 0.998 (≈ cosine)

#Практическое сравнение метрик

Напишите тест для сравнения:

import numpy as np from sklearn.metrics.pairwise import cosine_similarity from scipy.spatial import distance def compare_metrics(A, B, name=""): """Сравнение метрик для двух векторов.""" # Косинусное сходство cosine = cosine_similarity([A], [B])[0][0] # Евклидово расстояние euclidean = distance.euclidean(A, B) # Скалярное произведение dot = np.dot(A, B) print(f"\n{name}") print(f"Вектор A: {A}") print(f"Вектор B: {B}") print(f" Cosine Similarity: {cosine:.4f}") print(f" Euclidean Distance: {euclidean:.4f}") print(f" Dot Product: {dot:.4f}") # Пример 1: Похожие векторы A = np.array([0.5, 0.3, 0.2]) B = np.array([0.48, 0.32, 0.21]) compare_metrics(A, B, "Пример 1: Похожие векторы") # Пример 2: Векторы разной длины, но одного направления C = np.array([0.5, 0.3, 0.2]) D = np.array([1.0, 0.6, 0.4]) # В 2 раза длиннее compare_metrics(C, D, "Пример 2:Same направление, разная длина") # Пример 3: Противоположные векторы E = np.array([1.0, 0.0, 0.0]) F = np.array([-1.0, 0.0, 0.0]) compare_metrics(E, F, "Пример 3: Противоположные векторы")

Вывод:

Пример 1: Похожие векторы
Вектор A: [0.5 0.3 0.2]
Вектор B: [0.48 0.32 0.21]
  Cosine Similarity: 0.9978
  Euclidean Distance: 0.0374
  Dot Product: 0.3780

Пример 2: Same направление, разная длина
Вектор C: [0.5 0.3 0.2]
Вектор D: [1.  0.6 0.4]
  Cosine Similarity: 1.0000  # Идентичное направление!
  Euclidean Distance: 0.5385  # Дальше из-за разной длины
  Dot Product: 0.7600

Пример 3: Противоположные векторы
Вектор E: [1. 0. 0.]
Вектор F: [-1.  0.  0.]
  Cosine Similarity: -1.0000  # Противоположное направление
  Euclidean Distance: 2.0000
  Dot Product: -1.0000

#Выбор метрики: руководство

#Текстовый поиск

# ✅ Cosine Similarity — стандарт # Игнорирует длину документа, учитывает только направление documents = [ "Краткая статья о Python", # Короткий "Подробное руководство по Python с примерами" # Длинный ] query = "программирование на Python" # Cosine найдёт оба документа (направление схожее) # Euclidean отдаст предпочтение короткому (меньше расстояние)

#RAG с нормализованными векторами

# ✅ Dot Product — быстрее cosine # После нормализации эквивалентно косинусному сходству from sklearn.preprocessing import normalize embeddings = model.encode(documents) normalized = normalize(embeddings) # FAISS с Inner Product (быстрее) index = faiss.IndexFlatIP(dimension) index.add(normalized)

#Кластеризация

# ✅ Euclidean (L2) — K-means минимизирует L2 from sklearn.cluster import KMeans kmeans = KMeans(n_clusters=5, metric='euclidean') kmeans.fit(embeddings)

#Поиск дубликатов

# ✅ Cosine Similarity — направление важнее величины # Или Euclidean для точных дубликатов (расстояние = 0) def find_duplicates(embeddings, threshold=0.95): """Поиск дубликатов по косинусному сходству.""" from sklearn.metrics.pairwise import cosine_similarity sim_matrix = cosine_similarity(embeddings) duplicates = [] n = len(embeddings) for i in range(n): for j in range(i + 1, n): if sim_matrix[i, j] >= threshold: duplicates.append((i, j, sim_matrix[i, j])) return duplicates

#Эффективное вычисление сходства

#Матричные операции для пакетного поиска

Вместо цикла по векторам используйте матричные операции:

import numpy as np # 1000 векторов в базе database = np.random.rand(1000, 384) # 10 запросов queries = np.random.rand(10, 384) # ❌ Медленно: цикл similarities = [] for query in queries: for db_vec in database: sim = cosine_similarity([query], [db_vec]) similarities.append(sim) # ✅ Быстро: матричное умножение from sklearn.preprocessing import normalize database_norm = normalize(database) queries_norm = normalize(queries) # Скалярное произведение всех пар сразу similarities_matrix = np.dot(queries_norm, database_norm.T) # Форма: (10, 1000) — сходство для всех пар запрос-документ # Топ-5 для каждого запроса top_5_indices = np.argsort(similarities_matrix)[::-1][:, :5]

#Поиск топ-k ближайших соседей

def find_top_k(query, database, k=5): """ Найти k ближайших соседей для запроса. Args: query: Вектор запроса (384,) database: Матрица базы данных (n, 384) k: Количество результатов """ # Нормализация query_norm = query / np.linalg.norm(query) database_norm = database / np.linalg.norm(database, axis=1, keepdims=True) # Косинусное сходство через скалярное произведение similarities = np.dot(database_norm, query_norm) # Индексы топ-k top_k_indices = np.argsort(similarities)[::-1][:k] return top_k_indices, similarities[top_k_indices] # Использование database = np.random.rand(10000, 384) query = np.random.rand(384) indices, scores = find_top_k(query, database, k=5) print(f"Топ-5 индексов: {indices}") print(f"Сходство: {scores}")

#Интерпретация результатов

#Пороговые значения для cosine similarity

def interpret_cosine(score): """Интерпретация косинусного сходства.""" if score >= 0.9: return "Практически идентичные" elif score >= 0.7: return "Очень похожие" elif score >= 0.5: return "Умеренное сходство" elif score >= 0.3: return "Слабое сходство" elif score >= 0: return "Почти не связаны" else: return "Противоположные" # Примеры scores = [0.95, 0.82, 0.58, 0.35, 0.12, -0.25] for score in scores: print(f"{score:.2f} → {interpret_cosine(score)}")

Вывод:

0.95 → Практически идентичные
0.82 → Очень похожие
0.58 → Умеренное сходство
0.35 → Слабое сходство
0.12 → Почти не связаны
-0.25 → Противоположные

#Визуализация распределения сходства

import matplotlib.pyplot as plt def visualize_similarity_distribution(embeddings, sample_size=1000): """Визуализация распределения косинусного сходства.""" from sklearn.metrics.pairwise import cosine_similarity # Случайная выборка indices = np.random.choice(len(embeddings), sample_size, replace=False) sample = embeddings[indices] # Матрица сходства sim_matrix = cosine_similarity(sample) # Берём только верхний треугольник (без диагонали) upper_tri = sim_matrix[np.triu_indices(sample_size, k=1)] # Гистограмма plt.figure(figsize=(10, 6)) plt.hist(upper_tri, bins=50, alpha=0.7, edgecolor='black') plt.axvline(x=0.7, color='r', linestyle='--', label='0.7 (очень похожие)') plt.axvline(x=0.5, color='y', linestyle='--', label='0.5 (умеренное сходство)') plt.xlabel('Cosine Similarity') plt.ylabel('Количество пар') plt.title('Распределение косинусного сходства') plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # Использование embeddings = model.encode(documents) visualize_similarity_distribution(embeddings)

#Типичные ошибки

#1. Использование L2 для ненормализованных текстовых векторов

# ❌ Проблема: длинные документы будут дальше от коротких # даже при одинаковом содержании long_doc = model.encode(["текст"] * 100) # Длинный документ short_doc = model.encode(["текст"]) # Короткий distance_l2 = euclidean(long_doc, short_doc) # Большое расстояние! # ✅ Решение: используйте cosine similarity from sklearn.preprocessing import normalize long_norm = normalize(long_doc) short_norm = normalize(short_doc) similarity = cosine_similarity(long_norm, short_norm) # Близко к 1

#2. Сравнение расстояний от разных метрик

# ❌ Нельзя сравнивать значения разных метрик напрямую cosine_sim = 0.8 # Это хорошо? l2_distance = 1.5 # Это далеко? # ✅ Интерпретируйте в контексте метрики # Cosine 0.8 = очень похожие (диапазон -1 до 1) # L2 1.5 зависит от масштабирования данных

#3. Игнорирование нормализации для dot product

# ❌ Dot product без нормализации зависит от масштаба A = np.array([10, 10, 10]) B = np.array([1, 1, 1]) dot = np.dot(A, B) # 30 — большое, но не значит "похожие" # ✅ Нормализуйте перед dot product A_norm = A / np.linalg.norm(A) B_norm = B / np.linalg.norm(B) dot_norm = np.dot(A_norm, B_norm) # 1.0 — одинаковое направление

#4. Выбор метрики без учёта задачи

# ❌ Использование Euclidean для текстов разной длины documents = ["краткий текст", "очень длинный текст с тем же содержанием"] embeddings = model.encode(documents) # Euclidean покажет большое расстояние из-за разной длины distance = euclidean(embeddings[0], embeddings[1]) # ✅ Cosine игнорирует длину from sklearn.preprocessing import normalize normalized = normalize(embeddings) similarity = cosine_similarity(normalized) # Близко к 1

#Практическое задание: тест метрик

Создайте скрипт для тестирования метрик на ваших данных:

# metric_tester.py import numpy as np from sentence_transformers import SentenceTransformer from sklearn.metrics.pairwise import cosine_similarity from scipy.spatial import distance from sklearn.preprocessing import normalize class MetricTester: """Тестирование метрик сходства.""" def __init__(self, model_name='all-MiniLM-L6-v2'): self.model = SentenceTransformer(model_name) def encode(self, texts): """Генерация нормализованных эмбеддингов.""" embeddings = self.model.encode(texts) return normalize(embeddings) def compare_pair(self, text1, text2): """Сравнение двух текстов всеми метриками.""" embeddings = self.encode([text1, text2]) cosine = cosine_similarity([embeddings[0]], [embeddings[1]])[0][0] euclidean = distance.euclidean(embeddings[0], embeddings[1]) dot = np.dot(embeddings[0], embeddings[1]) print(f"\nТекст 1: {text1}") print(f"Текст 2: {text2}") print(f" Cosine Similarity: {cosine:.4f}") print(f" Euclidean Distance: {euclidean:.4f}") print(f" Dot Product: {dot:.4f}") return { 'cosine': cosine, 'euclidean': euclidean, 'dot': dot } def test_dataset(self, texts, labels): """Тестирование на наборе данных с метками.""" embeddings = self.encode(texts) # Матрица сходства sim_matrix = cosine_similarity(embeddings) # Среднее сходство внутри классов и между классами within_class = [] between_class = [] n = len(texts) for i in range(n): for j in range(i + 1, n): if labels[i] == labels[j]: within_class.append(sim_matrix[i, j]) else: between_class.append(sim_matrix[i, j]) print(f"\nСреднее сходство внутри классов: {np.mean(within_class):.3f}") print(f"Среднее сходство между классами: {np.mean(between_class):.3f}") print(f"Разделение: {np.mean(within_class) - np.mean(between_class):.3f}") # Использование if __name__ == "__main__": tester = MetricTester() # Тест пар tester.compare_pair("кот сидит на коврике", "кошка лежит на подстилке") tester.compare_pair("Python программирование", "веб-разработка") # Тест набора данных texts = [ "кот кошка питомец", "собака пёс питомец", "автомобиль машина транспорт", "мотоцикл байк транспорт" ] labels = [0, 0, 1, 1] # 0 = животные, 1 = транспорт tester.test_dataset(texts, labels)

#Чеклист: вы научились

  • Понимаете разницу между Cosine, Euclidean и Dot Product
  • Знаете, когда какую метрику использовать
  • Умеете вычислять сходство через матричные операции
  • Можете интерпретировать значения метрик
  • Избегаете типичных ошибок (нормализация, сравнение метрик)

#Что дальше

Теперь вы понимаете метрики сходства. Следующий шаг — FAISS: основы:

  • Создание индекса для поиска
  • Добавление векторов
  • Поиск ближайших соседей
  • Сохранение и загрузка индекса

#Домашнее задание

  1. Создайте файл metric_comparison.py с тестером метрик
  2. Возьмите 20+ документов, разбейте на 3-4 категории
  3. Протестируйте все три метрики
  4. Запишите, какая метрика лучше разделяет категории
  5. Попробуйте разные пороги для классификации

Готовы? Открывайте faiss_basics!

Далее: FAISS: основы для прототипов