Vector Math — Полный код¶
Уровень: L3 (deep-dive) | ← Назад к Embeddings
Файл: frontend/src/embeddings/vectorMath.ts¶
Математические операции над векторами для similarity search.
/**
* Cosine Similarity между двумя векторами.
*
* Формула:
* cos(θ) = (A · B) / (||A|| × ||B||)
*
* Где:
* A · B = Σ(aᵢ × bᵢ) — скалярное произведение
* ||A|| = √Σ(aᵢ²) — евклидова норма
*
* Возвращает значение в диапазоне [-1, 1]:
* 1 = идентичные направления (максимальное сходство)
* 0 = ортогональные (нет сходства)
* -1 = противоположные направления
*
* Для ResNet50 embeddings значения обычно в диапазоне [0.3, 0.99]
*/
export function cosineSimilarity(a: Float32Array, b: Float32Array): number {
if (a.length !== b.length || a.length === 0) return 0;
let dot = 0; // Скалярное произведение
let normA = 0; // Квадрат нормы A
let normB = 0; // Квадрат нормы B
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
const denom = Math.sqrt(normA) * Math.sqrt(normB);
if (!isFinite(dot) || denom === 0) return 0; // Защита от деления на ноль
return dot / denom;
}
/**
* Средний вектор (centroid) из набора векторов.
*
* Используется для вычисления embedding образа:
* outfit_embedding = mean([item1_emb, item2_emb, ...])
*
* Формула:
* mean[i] = (1/N) × Σ(vectors[j][i])
*
* Фильтрует вектора неправильной размерности.
*/
export function meanVector(vectors: Float32Array[]): Float32Array {
if (vectors.length === 0) return new Float32Array(0);
const dim = vectors[0].length;
const valid = vectors.filter((v) => v.length === dim);
if (valid.length === 0) return new Float32Array(0);
const result = new Float32Array(dim);
// Суммирование по всем векторам
for (const vec of valid) {
for (let i = 0; i < dim; i++) {
result[i] += vec[i];
}
}
// Деление на количество
const count = valid.length;
for (let i = 0; i < dim; i++) {
result[i] /= count;
}
return result;
}
/**
* Поиск TopK наиболее похожих кандидатов.
*
* Алгоритм:
* 1. Вычислить cosine similarity для каждого кандидата
* 2. Отсортировать по убыванию score
* 3. Вернуть первые topK результатов
*
* Сложность: O(N × D + N log N)
* N = количество кандидатов
* D = размерность вектора (1024)
*/
export function topKSimilar(
query: Float32Array,
candidates: Array<{ id: string; vec: Float32Array }>,
topK: number
): Array<{ id: string; score: number }> {
// Вычислить similarity для каждого кандидата
const scored = candidates.map((c) => ({
id: c.id,
score: cosineSimilarity(query, c.vec),
}));
// Сортировка по убыванию (наиболее похожие первыми)
scored.sort((a, b) => b.score - a.score);
// Вернуть только topK
return scored.slice(0, topK);
}
Математическое обоснование¶
Cosine Similarity vs Euclidean Distance¶
| Метрика | Формула | Свойства |
|---|---|---|
| Cosine Similarity | cos(θ) = (A·B)/(‖A‖×‖B‖) | Инвариантна к масштабу вектора |
| Euclidean Distance | ‖A-B‖ = √Σ(aᵢ-bᵢ)² | Чувствительна к масштабу |
Почему cosine: ResNet50 embeddings не нормализованы. Cosine similarity сравнивает направления векторов, игнорируя их длину. Это важно, т.к. вещи разных категорий могут иметь векторы разной магнитуды.
Mean Vector для образов¶
Интуиция: embedding образа — это "центр масс" embeddings его вещей в пространстве признаков.
Похожие образы — те, чьи центры масс расположены близко в embedding space.
Float32Array¶
Использование Float32Array вместо обычного number[]:
- 2× экономия памяти (32-bit vs 64-bit per element)
- Быстрее в циклах (типизированный массив, no boxing)
- Для 1024-dim vector: 4KB vs 8KB per embedding
Вычислительная сложность¶
Для гардероба из N=500 вещей, D=1024:
cosine similarity: O(D) = O(1024) ≈ 3072 операций
topK search: O(N×D + N×log(N)) = 500×1024 + 500×log(500) ≈ 516000 операций
На современном CPU в Web Worker: ~2-3ms для 500 вещей.