← Назад к вопросам

Какое свойство cosine similarity делает его предпочтительным методом поиска ближайших соседей?

1.0 Junior🔥 161 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какое свойство cosine similarity делает его предпочтительным методом поиска ближайших соседей?

Определение Cosine Similarity

Cosine similarity — это мера сходства между двумя векторами, основанная на косинусе угла между ними. Формула:

cos(θ) = (A · B) / (||A|| * ||B||) = Σ(A_i * B_i) / (√Σ(A_i²) * √Σ(B_i²))

Результат находится в диапазоне [-1, 1] (для ненормализованных векторов) или [0, 1] (для положительных векторов):

  • 1 = векторы совпадают по направлению (максимальное сходство)
  • 0 = векторы перпендикулярны (нет сходства)
  • -1 = векторы противоположны (минимальное сходство)
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# Два вектора
A = np.array([1, 2, 3])
B = np.array([2, 4, 6])  # B = 2*A, параллельны
C = np.array([1, 0, 0])  # Перпендикулярно A

# Cosine similarity между A и B
sim_AB = cosine_similarity([A], [B])[0, 0]
print(f"Cosine(A, B) = {sim_AB:.4f}")  # 1.0 (параллельны)

# Cosine similarity между A и C
sim_AC = cosine_similarity([A], [C])[0, 0]
print(f"Cosine(A, C) = {sim_AC:.4f}")  # 0.267 (менее похожи)

ГЛАВНОЕ СВОЙСТВО: Инвариантность к масштабированию (Scale Invariance)

Это самое важное свойство, делающее cosine similarity предпочтительным для поиска соседей. Cosine similarity не зависит от длины (нормы) векторов — она зависит только от их направления.

# Пример: две точки данных
A = np.array([1, 2, 3])
B = np.array([2, 4, 6])  # Точно такое же направление, но в 2 раза больше по величине

# Cosine similarity: одинаковая
sim_AB = cosine_similarity([A], [B])[0, 0]
print(f"Cosine(A, B) = {sim_AB:.4f}")  # 1.0

# Но Euclidean distance: очень разные!
from sklearn.metrics.pairwise import euclidean_distances
dist_AB = euclidean_distances([A], [B])[0, 0]
print(f"Euclidean(A, B) = {dist_AB:.4f}")  # 7.07

# Масштабированный вектор
B_scaled = np.array([10, 20, 30])  # В 10 раз больше
sim_scaled = cosine_similarity([A], [B_scaled])[0, 0]
print(f"Cosine(A, B_scaled) = {sim_scaled:.4f}")  # Всё ещё 1.0!

dist_scaled = euclidean_distances([A], [B_scaled])[0, 0]
print(f"Euclidean(A, B_scaled) = {dist_scaled:.4f}")  # 70.7 — совсем другое

Это критично для текстовых и высокомерных данных, где "длина" вектора может быть результатом артефактов данных, а не важной информацией.

Почему это важно для поиска соседей

1. Текстовое подобие (NLP)

В текстовых данных (bag-of-words, TF-IDF) важно сходство по составу слов, а не по абсолютной частоте.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# Документы
docs = [
    "machine learning algorithms",
    "machine learning",  # Короче, но о том же
    "deep learning algorithms",
]

vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(docs)

# Cosine similarity
sim_01 = cosine_similarity(X[0:1], X[1:2])[0, 0]
sim_02 = cosine_similarity(X[0:1], X[2:3])[0, 0]

print(f"Cosine(doc0, doc1) = {sim_01:.4f}")  # ~0.81 (похожи)
print(f"Cosine(doc0, doc2) = {sim_02:.4f}")  # ~0.5 (менее похожи)

# Несмотря на разную длину документов, cosine правильно ранжирует сходство

2. Рекомендационные системы (word embeddings, sentence embeddings)

# Два embedding'а слов (например, из Word2Vec)
word_apple = np.array([0.2, 0.5, 0.3, 0.8])
word_orange = np.array([0.15, 0.52, 0.25, 0.78])  # Похожий вектор
word_car = np.array([0.9, 0.1, 0.2, 0.05])  # Не похожий

sim_fruit = cosine_similarity([word_apple], [word_orange])[0, 0]
sim_different = cosine_similarity([word_apple], [word_car])[0, 0]

print(f"Cosine(apple, orange) = {sim_fruit:.4f}")  # ~0.99 (похожи)
print(f"Cosine(apple, car) = {sim_different:.4f}")  # ~0.35 (не похожи)

# Cosine зависит ТОЛЬКО от направления, не от абсолютной величины

Сравнение с другими метриками расстояния

from sklearn.metrics.pairwise import euclidean_distances, manhattan_distances
import pandas as pd

A = np.array([[1, 2, 3]])
B = np.array([[2, 4, 6]])  # Масштабированный вектор
C = np.array([[1, 0, 0]])  # Другое направление

print("Метрики расстояния:")
print(f"A = {A[0]}")
print(f"B = {B[0]} (масштабированный A)")
print(f"C = {C[0]} (другое направление)\n")

# Cosine similarity (1 - cos_dist)
cos_AB = cosine_similarity(A, B)[0, 0]
cos_AC = cosine_similarity(A, C)[0, 0]
print(f"Cosine similarity:")
print(f"  Cosine(A, B) = {cos_AB:.4f}  <- ИДЕНТИЧНОЕ НАПРАВЛЕНИЕ")
print(f"  Cosine(A, C) = {cos_AC:.4f}")

# Euclidean distance (очень чувствителен к масштабу)
euc_AB = euclidean_distances(A, B)[0, 0]
euc_AC = euclidean_distances(A, C)[0, 0]
print(f"\nEuclidean distance:")
print(f"  Euclidean(A, B) = {euc_AB:.4f}  <- БОЛЬШОЕ РАССТОЯНИЕ!")
print(f"  Euclidean(A, C) = {euc_AC:.4f}")

# Manhattan distance (также чувствителен к масштабу)
man_AB = manhattan_distances(A, B)[0, 0]
man_AC = manhattan_distances(A, C)[0, 0]
print(f"\nManhattan distance:")
print(f"  Manhattan(A, B) = {man_AB:.4f}  <- БОЛЬШОЕ РАССТОЯНИЕ!")
print(f"  Manhattan(A, C) = {man_AC:.4f}")

Вывод: cosine similarity правильно считает, что A и B очень похожи (косинус = 1.0), но Euclidean и Manhattan дают большое расстояние из-за масштаба.

3. Инвариантность к нормализации

Часто данные нормализуются по разным причинам (разные единицы измерения). Cosine similarity считает нормализованные и ненормализованные данные эквивалентными по направлению.

from sklearn.preprocessing import StandardScaler, MinMaxScaler

X = np.array([[1, 2, 3],
              [2, 4, 6],
              [10, 20, 30]])

# Cosine на оригинальных данных
cos_01_orig = cosine_similarity(X[0:1], X[1:2])[0, 0]
cos_02_orig = cosine_similarity(X[0:1], X[2:3])[0, 0]

print(f"Оригинальные данные:")
print(f"  Cosine(X0, X1) = {cos_01_orig:.4f}")
print(f"  Cosine(X0, X2) = {cos_02_orig:.4f}")

# Нормализация StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
cos_01_scaled = cosine_similarity(X_scaled[0:1], X_scaled[1:2])[0, 0]
cos_02_scaled = cosine_similarity(X_scaled[0:1], X_scaled[2:3])[0, 0]

print(f"\nПосле StandardScaler:")
print(f"  Cosine(X0, X1) = {cos_01_scaled:.4f}")
print(f"  Cosine(X0, X2) = {cos_02_scaled:.4f}")

print(f"\nРазница: {abs(cos_01_orig - cos_01_scaled):.6f}")  # ~0

4. Эффективность в высокомерном пространстве

В высоких размерностях (embedding'и, текстовые данные) cosine similarity часто работает лучше, чем Euclidean.

# Почему? В высоких размерностях происходит "проклятие размерности"
# Все расстояния становятся примерно одинаковыми
# Но cosine сохраняет различия в направлении

from sklearn.neighbors import NearestNeighbors

# Высокомерные данные (embeddings)
np.random.seed(42)
X = np.random.randn(1000, 128)  # 1000 samples, 128 dimensions

# KNN с Euclidean
knn_euclidean = NearestNeighbors(n_neighbors=5, metric='euclidean')
knn_euclidean.fit(X)
distances_euc, indices_euc = knn_euclidean.kneighbors(X[0:1])
print(f"Euclidean расстояния: {distances_euc[0]}")
print(f"Разброс: {distances_euc[0].max() - distances_euc[0].min():.4f}")

# KNN с Cosine
knn_cosine = NearestNeighbors(n_neighbors=5, metric='cosine')
knn_cosine.fit(X)
distances_cos, indices_cos = knn_cosine.kneighbors(X[0:1])
print(f"\nCosine расстояния: {distances_cos[0]}")
print(f"Разброс: {distances_cos[0].max() - distances_cos[0].min():.4f}")

print(f"\nCosine лучше различает соседей в высоких размерностях!")

Практический пример: поиск похожих документов

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.neighbors import NearestNeighbors

# Документы
documents = [
    "python machine learning classification",
    "deep learning neural networks",
    "python programming tutorial",
    "machine learning algorithms basics",
    "web development javascript html"
]

# Vectorize
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)

# Найти 3 ближайших соседей для первого документа
knn = NearestNeighbors(n_neighbors=4, metric='cosine')
knn.fit(X)

distances, indices = knn.kneighbors(X[0:1])

print(f"Query: {documents[0]}\n")
print("3 ближайших соседа (кроме самого себя):")
for i in range(1, 4):
    similarity = 1 - distances[0][i]  # Преобразовать distance в similarity
    print(f"{i}. {documents[indices[0][i]]} (similarity: {similarity:.4f})")

Резюме: Главное преимущество

Главное свойство cosine similarityинвариантность к масштабированию вектора. Она зависит только от НАПРАВЛЕНИЯ, а не от ВЕЛИЧИНЫ.

Это делает cosine similarity идеальной для:

  • Текстовых данных (TF-IDF, bag-of-words)
  • Embedding'ов и представлений (word2vec, BERT, image embeddings)
  • Любых данных высокой размерности
  • Случаев, где абсолютная величина вектора не важна

Этим она отличается от Euclidean и Manhattan расстояний, которые очень чувствительны к масштабированию и работают лучше на низкомерных данных, где абсолютные значения имеют значение.