es una métrica comúnmente utilizada para operacionalizar tareas, como la búsqueda semántica y la comparación de documentos en el campo del procesamiento del lenguaje natural (PNL). Los cursos introductorios de PNL a menudo proporcionan solo una justificación de alto nivel para usar similitud coseno en tales tareas (en lugar de, por ejemplo, la distancia euclidiana) sin explicar las matemáticas subyacentes, dejando a muchos científicos de datos con una comprensión bastante vaga del tema. Para abordar esta brecha, el siguiente artículo establece la intuición matemática detrás de la métrica de similitud de coseno y muestra cómo esto puede ayudarnos a interpretar los resultados en la práctica con ejemplos prácticos en Python.
Nota: Todas las figuras y fórmulas en las siguientes secciones han sido creadas por el autor de este artículo.
Intuición matemática
La métrica de similitud de coseno se basa en la función coseno que los lectores pueden recordar de las matemáticas de la escuela secundaria. La función coseno exhibe un patrón de wavelike repetitivo, un ciclo completo del cual se representa en la Figura 1 a continuación para el rango 0 <= incógnita <= 2*pi. El código de Python utilizado para producir la figura también se incluye como referencia.
import numpy as np
import matplotlib.pyplot as plt
# Define the x range from 0 to 2*pi
x = np.linspace(0, 2 * np.pi, 500)
y = np.cos(x)
# Create the plot
plt.figure(figsize=(8, 4))
plt.plot(x, y, label='cos(x)', color='blue')
# Add notches on the x-axis at pi/2 and 3*pi/2
notch_positions = [0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi]
notch_labels = ['0', 'pi/2', 'pi', '3*pi/2', '2*pi']
plt.xticks(ticks=notch_positions, labels=notch_labels)
# Add custom horizontal gridlines only at y = -1, 0, 1
for y_val in [-1, 0, 1]:
plt.axhline(y=y_val, color='gray', linestyle='--', linewidth=0.5)
# Add vertical gridlines at specified x-values
for x_val in notch_positions:
plt.axvline(x=x_val, color='gray', linestyle='--', linewidth=0.5)
# Customize the plot
plt.xlabel("x")
plt.ylabel("cos(x)")
# Final layout and display
plt.tight_layout()
plt.show()
El parámetro de función incógnita denota un ángulo en radianes (por ejemplo, el ángulo entre dos vectores en un espacio de incrustación), donde pi/2, pi3*pi/2 y 2*pison 90, 180, 270 y 360 grados, respectivamente.
Para comprender por qué la función coseno puede servir como una base útil para diseñar una métrica de similitud vectorial, observe que la función coseno básica, sin ninguna transformación funcional como se muestra en la Figura 1, tiene máximos en x = 2*a*pimínimos en x = (2*b + 1)*piy raíces en x = (do + 1/2)*pi para algunos enteros a, by do. En otras palabras, si incógnita denota el ángulo entre dos vectores, cos (x) Devuelve el valor más grande cuando los vectores apuntan en la misma dirección, el valor más pequeño cuando los vectores apuntan en direcciones opuestas y cero cuando los vectores son ortogonales entre sí.
Este comportamiento de la función coseno captura claramente la interacción entre dos conceptos clave en PNL: superposición semántica (transmitir cuánto significado se comparte entre dos textos) y polaridad semántica (Capturar la oposición del significado en los textos). Por ejemplo, los textos “Me gustó esta película” y “Disfruté de esta película” tendrían una alta superposición semántica (expresan esencialmente el mismo significado a pesar de usar diferentes palabras) y baja polaridad semántica (no expresan significados opuestos). Ahora, si los vectores de incrustación para dos palabras codifican tanto la superposición semántica como la polaridad, entonces esperaríamos que los sinónimos tengan una similitud cosena que se acerca a 1, los antónimos que tienen una similitud coseno que se acerca -1 y palabras no relacionadas para tener una similitud cosena que se acerca a 0.
En la práctica, normalmente no sabremos el ángulo incógnita directamente. En cambio, debemos derivar el valor coseno de los vectores mismos. Dados dos vectores U y Vcada uno con norte Los elementos, el coseno del ángulo entre estos vectores, equivalente a la métrica de similitud coseno, se calcula como el producto DOT de los vectores divididos por el producto de las magnitudes vectoriales:
La fórmula anterior para el coseno del ángulo entre dos vectores puede derivarse de la llamada regla de coseno, como se demuestra en el segmento entre los minutos 12 y 18 de este video:
En este video se presenta una prueba ordenada de la regla de coseno en este video:
La siguiente implementación de Python de similitud de coseno operacionaliza explícitamente las fórmulas presentadas anteriormente, sin confiar en ningún paquete de terceros en caja negra:
import math
def cosine_similarity(U, V):
if len(U) != len(V):
raise ValueError("Vectors must be of the same length.")
# Compute dot product and magnitudes
dot_product = sum(u * v for u, v in zip(U, V))
magnitude_U = math.sqrt(sum(u ** 2 for u in U))
magnitude_V = math.sqrt(sum(v ** 2 for v in V))
# Zero vector handling to avoid division by zero
if magnitude_U == 0 or magnitude_V == 0:
raise ValueError("Cannot compute cosine similarity for zero-magnitude vectors.")
return dot_product / (magnitude_U * magnitude_V)
Los lectores interesados pueden referirse a este Artículo para una implementación de Python más eficiente del distancia coseno Métrica (definida como 1 similitud de coseno) utilizando los paquetes Numpy y Scipy.
Finalmente, vale la pena comparar la intuición matemática de la similitud (o distancia) coseno con la de Distancia euclidianaque mide la distancia lineal entre dos vectores y también puede servir como una métrica de similitud vectorial. En particular, cuanto menor sea la distancia euclidiana entre dos vectores, mayor es probable que sea su similitud semántica. La distancia euclidiana entre dos vectores U y V (cada uno de longitud norte) se puede calcular utilizando la siguiente fórmula:
A continuación se muestra la implementación de Python correspondiente:
import math
def euclidean_distance(U, V):
if len(U) != len(V):
raise ValueError("Vectors must be of the same length.")
# Compute sum of squared differences
sum_squared_diff = sum((u - v) ** 2 for u, v in zip(U, V))
# Take the square root of the sum
return math.sqrt(sum_squared_diff)
Observe que, dado que las diferencias de elementos en la fórmula de distancia euclidiana están cuadrados, la métrica resultante siempre será un número no negativo, cero si los vectores son idénticos, positivos de lo contrario. En el contexto de PNL, esto implica que la distancia euclidiana no reflejará la polaridad semántica de la misma manera que la distancia coseno. Además, mientras dos vectores apunten en la misma dirección, el coseno del ángulo entre ellos seguirá siendo el mismo independientemente de las magnitudes del vector. Por el contrario, la métrica de distancia euclidiana se ve afectada por las diferencias en la magnitud del vector, lo que puede conducir a interpretaciones engañosas en la práctica (por ejemplo, dos textos de diferentes longitudes pueden producir una alta distancia euclidiana a pesar de ser semánticamente similar). Como tal, la similitud de coseno es la métrica preferida en muchos escenarios de PNL, donde determinar la direccionalidad del vector o semántico es la principal preocupación.
Teoría versus práctica
En un escenario práctico de la PNL, la interpretación de la similitud de coseno depende de la medida en que el vector integrado codifica la polaridad y la superposición semántica. En el siguiente ejemplo práctico, investigaremos la similitud entre dos palabras dadas utilizando un modelo de incrustación previa a la aparición que no codifica la polaridad (All-Minilm-L6-V2) y uno que lo hace (Distilbert-Base-Finetuned-SST-2-Inglés). También utilizaremos implementaciones más eficientes de similitud de coseno y distancia euclidiana aprovechando las funciones proporcionadas por el paquete SciPy.
from scipy.spatial.distance import cosine as cosine_distance
from sentence_transformers import SentenceTransformer
from transformers import AutoTokenizer, AutoModel
import torch
# Words to embed
words = ["movie", "film", "good", "bad", "spoon", "car"]
# Load a pre-trained embedding model from Hugging Face
model_1 = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
model_2_name = "distilbert-base-uncased-finetuned-sst-2-english"
model_2_tokenizer = AutoTokenizer.from_pretrained(model_2_name)
model_2 = AutoModel.from_pretrained(model_2_name)
# Generate embeddings for model 1
embeddings_1 = dict(zip(words, model_1.encode(words)))
# Generate embeddings for model 2
inputs = model_2_tokenizer(words, padding=True, truncation=True, return_tensors="pt")
with torch.no_grad():
outputs = model_2(**inputs)
embedding_vectors_model_2 = outputs.last_hidden_state.mean(dim=1)
embeddings_2 = {word: vector for word, vector in zip(words, embedding_vectors_model_2)}
# Compute and print cosine similarity (1 - cosine distance) for both embedding models
print("Cosine similarity for embedding model 1:")
print("movie", "\t", "film", "\t", 1 - cosine_distance(embeddings_1["movie"], embeddings_1["film"]))
print("good", "\t", "bad", "\t", 1 - cosine_distance(embeddings_1["good"], embeddings_1["bad"]))
print("spoon", "\t", "car", "\t", 1 - cosine_distance(embeddings_1["spoon"], embeddings_1["car"]))
print()
print("Cosine similarity for embedding model 2:")
print("movie", "\t", "film", "\t", 1 - cosine_distance(embeddings_2["movie"], embeddings_2["film"]))
print("good", "\t", "bad", "\t", 1 - cosine_distance(embeddings_2["good"], embeddings_2["bad"]))
print("spoon", "\t", "car", "\t", 1 - cosine_distance(embeddings_2["spoon"], embeddings_2["car"]))
print()
Producción:
Cosine similarity for embedding model 1:
movie film 0.8426464702276286
good bad 0.5871497042685934
spoon car 0.22919675707817078
Cosine similarity for embedding model 2:
movie film 0.9638281550070811
good bad -0.3416433451550165
spoon car 0.5418748837234599
Las palabras “película” y “película”, que generalmente se usan como sinónimos, tienen una similitud coseno cercana a 1, lo que sugiere una alta superposición semántica como se esperaba. Las palabras “buenas” y “malas” son antónimos, y vemos esto reflejado en el resultado de similitud de coseno negativo cuando se usa el segundo modelo de incrustación que se sabe que codifica la polaridad semántica. Finalmente, las palabras “Spoon” y “Car” están semánticamente no relacionadas, y la ortogonalidad correspondiente de sus integridades vectoriales está indicada por sus resultados de similitud coseno más cercanos a cero que para “película” y “película”.
La envoltura
La similitud coseno entre dos vectores se basa en el coseno del ángulo que forman y, a diferencia de las métricas como la distancia euclidiana, no es sensible a las diferencias en las magnitudes vectoriales. En teoría, la similitud de coseno debe estar cerca de 1 si los vectores apuntan en la misma dirección (que indican una alta similitud), cerca de -1 si los vectores apuntan en direcciones opuestas (que indican una alta disimilitud) y cerca de 0 si los vectores son ortogonales (indicando no recelación). Sin embargo, la interpretación exacta de la similitud cosena en un escenario de PNL dado depende de la naturaleza del modelo de incrustación utilizado para vectorizar los datos textuales (por ejemplo, si el modelo de incrustación codifica la polaridad además de la superposición semántica).