- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
389 lines
9.7 KiB
Python
389 lines
9.7 KiB
Python
"""
|
|
Similarity - Calculs de Similarité et Distance
|
|
|
|
Fonctions pour calculer différentes métriques de similarité et distance
|
|
entre vecteurs d'embeddings.
|
|
"""
|
|
|
|
import numpy as np
|
|
from typing import Union, List
|
|
|
|
|
|
def cosine_similarity(vec1: np.ndarray, vec2: np.ndarray) -> float:
|
|
"""
|
|
Calculer similarité cosinus entre deux vecteurs
|
|
|
|
similarity = (vec1 · vec2) / (||vec1|| * ||vec2||)
|
|
|
|
Args:
|
|
vec1: Premier vecteur
|
|
vec2: Deuxième vecteur
|
|
|
|
Returns:
|
|
Similarité cosinus dans [-1, 1]
|
|
1 = identiques, 0 = orthogonaux, -1 = opposés
|
|
|
|
Raises:
|
|
ValueError: Si dimensions ne correspondent pas
|
|
"""
|
|
if vec1.shape != vec2.shape:
|
|
raise ValueError(
|
|
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
|
|
)
|
|
|
|
# Produit scalaire
|
|
dot_product = np.dot(vec1, vec2)
|
|
|
|
# Normes
|
|
norm1 = np.linalg.norm(vec1)
|
|
norm2 = np.linalg.norm(vec2)
|
|
|
|
# Éviter division par zéro
|
|
if norm1 == 0 or norm2 == 0:
|
|
return 0.0
|
|
|
|
# Similarité cosinus
|
|
similarity = dot_product / (norm1 * norm2)
|
|
|
|
# Clamp dans [-1, 1] pour éviter erreurs numériques
|
|
similarity = np.clip(similarity, -1.0, 1.0)
|
|
|
|
return float(similarity)
|
|
|
|
|
|
def euclidean_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
|
|
"""
|
|
Calculer distance euclidienne (L2) entre deux vecteurs
|
|
|
|
distance = ||vec1 - vec2||_2 = sqrt(sum((vec1 - vec2)^2))
|
|
|
|
Args:
|
|
vec1: Premier vecteur
|
|
vec2: Deuxième vecteur
|
|
|
|
Returns:
|
|
Distance euclidienne (>= 0)
|
|
|
|
Raises:
|
|
ValueError: Si dimensions ne correspondent pas
|
|
"""
|
|
if vec1.shape != vec2.shape:
|
|
raise ValueError(
|
|
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
|
|
)
|
|
|
|
return float(np.linalg.norm(vec1 - vec2))
|
|
|
|
|
|
def manhattan_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
|
|
"""
|
|
Calculer distance de Manhattan (L1) entre deux vecteurs
|
|
|
|
distance = sum(|vec1 - vec2|)
|
|
|
|
Args:
|
|
vec1: Premier vecteur
|
|
vec2: Deuxième vecteur
|
|
|
|
Returns:
|
|
Distance de Manhattan (>= 0)
|
|
|
|
Raises:
|
|
ValueError: Si dimensions ne correspondent pas
|
|
"""
|
|
if vec1.shape != vec2.shape:
|
|
raise ValueError(
|
|
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
|
|
)
|
|
|
|
return float(np.sum(np.abs(vec1 - vec2)))
|
|
|
|
|
|
def dot_product(vec1: np.ndarray, vec2: np.ndarray) -> float:
|
|
"""
|
|
Calculer produit scalaire entre deux vecteurs
|
|
|
|
dot = vec1 · vec2 = sum(vec1 * vec2)
|
|
|
|
Args:
|
|
vec1: Premier vecteur
|
|
vec2: Deuxième vecteur
|
|
|
|
Returns:
|
|
Produit scalaire
|
|
|
|
Raises:
|
|
ValueError: Si dimensions ne correspondent pas
|
|
"""
|
|
if vec1.shape != vec2.shape:
|
|
raise ValueError(
|
|
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
|
|
)
|
|
|
|
return float(np.dot(vec1, vec2))
|
|
|
|
|
|
def normalize_l2(vector: np.ndarray, epsilon: float = 1e-10) -> np.ndarray:
|
|
"""
|
|
Normaliser un vecteur avec norme L2
|
|
|
|
normalized = vector / ||vector||_2
|
|
|
|
Args:
|
|
vector: Vecteur à normaliser
|
|
epsilon: Valeur minimale pour éviter division par zéro
|
|
|
|
Returns:
|
|
Vecteur normalisé (norme L2 = 1.0)
|
|
"""
|
|
norm = np.linalg.norm(vector)
|
|
if norm < epsilon:
|
|
return vector
|
|
return vector / norm
|
|
|
|
|
|
def normalize_l1(vector: np.ndarray, epsilon: float = 1e-10) -> np.ndarray:
|
|
"""
|
|
Normaliser un vecteur avec norme L1
|
|
|
|
normalized = vector / sum(|vector|)
|
|
|
|
Args:
|
|
vector: Vecteur à normaliser
|
|
epsilon: Valeur minimale pour éviter division par zéro
|
|
|
|
Returns:
|
|
Vecteur normalisé (norme L1 = 1.0)
|
|
"""
|
|
norm = np.sum(np.abs(vector))
|
|
if norm < epsilon:
|
|
return vector
|
|
return vector / norm
|
|
|
|
|
|
def batch_cosine_similarity(vectors: List[np.ndarray],
|
|
query: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Calculer similarité cosinus entre une requête et un batch de vecteurs
|
|
|
|
Args:
|
|
vectors: Liste de vecteurs
|
|
query: Vecteur de requête
|
|
|
|
Returns:
|
|
Array de similarités
|
|
"""
|
|
# Convertir en matrice
|
|
matrix = np.array(vectors)
|
|
|
|
# Normaliser
|
|
matrix_norm = matrix / (np.linalg.norm(matrix, axis=1, keepdims=True) + 1e-10)
|
|
query_norm = query / (np.linalg.norm(query) + 1e-10)
|
|
|
|
# Produit matriciel
|
|
similarities = np.dot(matrix_norm, query_norm)
|
|
|
|
# Clamp
|
|
similarities = np.clip(similarities, -1.0, 1.0)
|
|
|
|
return similarities
|
|
|
|
|
|
def pairwise_cosine_similarity(vectors: List[np.ndarray]) -> np.ndarray:
|
|
"""
|
|
Calculer matrice de similarité cosinus entre tous les vecteurs
|
|
|
|
Args:
|
|
vectors: Liste de vecteurs
|
|
|
|
Returns:
|
|
Matrice de similarité (n x n)
|
|
"""
|
|
# Convertir en matrice
|
|
matrix = np.array(vectors)
|
|
|
|
# Normaliser
|
|
matrix_norm = matrix / (np.linalg.norm(matrix, axis=1, keepdims=True) + 1e-10)
|
|
|
|
# Produit matriciel
|
|
similarity_matrix = np.dot(matrix_norm, matrix_norm.T)
|
|
|
|
# Clamp
|
|
similarity_matrix = np.clip(similarity_matrix, -1.0, 1.0)
|
|
|
|
return similarity_matrix
|
|
|
|
|
|
def angular_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
|
|
"""
|
|
Calculer distance angulaire entre deux vecteurs
|
|
|
|
distance = arccos(cosine_similarity) / π
|
|
|
|
Args:
|
|
vec1: Premier vecteur
|
|
vec2: Deuxième vecteur
|
|
|
|
Returns:
|
|
Distance angulaire dans [0, 1]
|
|
"""
|
|
similarity = cosine_similarity(vec1, vec2)
|
|
angle = np.arccos(np.clip(similarity, -1.0, 1.0))
|
|
return float(angle / np.pi)
|
|
|
|
|
|
def jaccard_similarity(vec1: np.ndarray, vec2: np.ndarray) -> float:
|
|
"""
|
|
Calculer similarité de Jaccard pour vecteurs binaires
|
|
|
|
similarity = |intersection| / |union|
|
|
|
|
Args:
|
|
vec1: Premier vecteur binaire
|
|
vec2: Deuxième vecteur binaire
|
|
|
|
Returns:
|
|
Similarité de Jaccard dans [0, 1]
|
|
"""
|
|
if vec1.shape != vec2.shape:
|
|
raise ValueError(
|
|
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
|
|
)
|
|
|
|
intersection = np.sum(np.logical_and(vec1, vec2))
|
|
union = np.sum(np.logical_or(vec1, vec2))
|
|
|
|
if union == 0:
|
|
return 0.0
|
|
|
|
return float(intersection / union)
|
|
|
|
|
|
def hamming_distance(vec1: np.ndarray, vec2: np.ndarray) -> float:
|
|
"""
|
|
Calculer distance de Hamming pour vecteurs binaires
|
|
|
|
distance = nombre de positions différentes
|
|
|
|
Args:
|
|
vec1: Premier vecteur binaire
|
|
vec2: Deuxième vecteur binaire
|
|
|
|
Returns:
|
|
Distance de Hamming
|
|
"""
|
|
if vec1.shape != vec2.shape:
|
|
raise ValueError(
|
|
f"Vectors must have same shape: {vec1.shape} vs {vec2.shape}"
|
|
)
|
|
|
|
return float(np.sum(vec1 != vec2))
|
|
|
|
|
|
# ============================================================================
|
|
# Fonctions de conversion
|
|
# ============================================================================
|
|
|
|
def similarity_to_distance(similarity: float,
|
|
method: str = "cosine") -> float:
|
|
"""
|
|
Convertir similarité en distance
|
|
|
|
Args:
|
|
similarity: Valeur de similarité
|
|
method: Méthode ("cosine", "angular")
|
|
|
|
Returns:
|
|
Distance correspondante
|
|
"""
|
|
if method == "cosine":
|
|
# distance = 1 - similarity (pour cosine dans [0, 1])
|
|
return 1.0 - similarity
|
|
elif method == "angular":
|
|
# distance angulaire
|
|
angle = np.arccos(np.clip(similarity, -1.0, 1.0))
|
|
return float(angle / np.pi)
|
|
else:
|
|
raise ValueError(f"Unknown method: {method}")
|
|
|
|
|
|
def distance_to_similarity(distance: float,
|
|
method: str = "euclidean") -> float:
|
|
"""
|
|
Convertir distance en similarité
|
|
|
|
Args:
|
|
distance: Valeur de distance
|
|
method: Méthode ("euclidean", "manhattan")
|
|
|
|
Returns:
|
|
Similarité correspondante dans [0, 1]
|
|
"""
|
|
if method in ["euclidean", "manhattan"]:
|
|
# similarity = 1 / (1 + distance)
|
|
return 1.0 / (1.0 + distance)
|
|
else:
|
|
raise ValueError(f"Unknown method: {method}")
|
|
|
|
|
|
# ============================================================================
|
|
# Fonctions utilitaires
|
|
# ============================================================================
|
|
|
|
def is_normalized(vector: np.ndarray,
|
|
norm_type: str = "l2",
|
|
tolerance: float = 1e-6) -> bool:
|
|
"""
|
|
Vérifier si un vecteur est normalisé
|
|
|
|
Args:
|
|
vector: Vecteur à vérifier
|
|
norm_type: Type de norme ("l2" ou "l1")
|
|
tolerance: Tolérance pour la vérification
|
|
|
|
Returns:
|
|
True si normalisé, False sinon
|
|
"""
|
|
if norm_type == "l2":
|
|
norm = np.linalg.norm(vector)
|
|
elif norm_type == "l1":
|
|
norm = np.sum(np.abs(vector))
|
|
else:
|
|
raise ValueError(f"Unknown norm type: {norm_type}")
|
|
|
|
return abs(norm - 1.0) < tolerance
|
|
|
|
|
|
def compute_centroid(vectors: List[np.ndarray]) -> np.ndarray:
|
|
"""
|
|
Calculer le centroïde (moyenne) d'un ensemble de vecteurs
|
|
|
|
Args:
|
|
vectors: Liste de vecteurs
|
|
|
|
Returns:
|
|
Vecteur centroïde
|
|
"""
|
|
if not vectors:
|
|
raise ValueError("Cannot compute centroid of empty list")
|
|
|
|
matrix = np.array(vectors)
|
|
return np.mean(matrix, axis=0)
|
|
|
|
|
|
def compute_variance(vectors: List[np.ndarray]) -> float:
|
|
"""
|
|
Calculer la variance d'un ensemble de vecteurs
|
|
|
|
Args:
|
|
vectors: Liste de vecteurs
|
|
|
|
Returns:
|
|
Variance totale
|
|
"""
|
|
if not vectors:
|
|
raise ValueError("Cannot compute variance of empty list")
|
|
|
|
matrix = np.array(vectors)
|
|
return float(np.var(matrix))
|