Cómo encontrar vecinos más cercanos aproximados, crear índices vectoriales y consultar incorporaciones vectoriales

En esta página, se describe cómo encontrar vecinos más cercanos aproximados (ANN), crear índices de vectores y consultar incorporaciones de vectores con las siguientes funciones de distancia de ANN en Spanner:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

Cuando un conjunto de datos es pequeño, puedes usar k-vecinos más cercanos (KNN) para encontrar los vectores k-más cercanos exactos. Sin embargo, a medida que crece tu conjunto de datos, también aumentan la latencia y el costo de una búsqueda de KNN. Puedes usar ANN para encontrar los vecinos aproximados más cercanos con una latencia y un costo significativamente reducidos.

K-vecinos más cercanos aproximados

En una búsqueda de ANN, los vectores que se muestran no son los verdaderos vecinos Top-K más cercanos porque la búsqueda de ANN calcula distancias aproximadas y es posible que no analice todos los vectores del conjunto de datos. En ocasiones, se muestran algunos vectores que no están entre los k vecinos más cercanos. Esto se conoce como pérdida de recuperación. La cantidad de pérdida de recuperación que consideres aceptable depende del caso de uso, pero, en la mayoría de los casos, perder un poco de recuperación a cambio de un mejor rendimiento de la base de datos es una compensación aceptable.

Para obtener más detalles sobre las funciones de distancia aproximada de Spanner, consulta lo siguiente:

Índice vectorial

Spanner acelera las búsquedas de vectores de ANN con un índice de vectores especializado. Este índice aprovecha el vecino más cercano escalable (ScaNN) de Google Research, un algoritmo de vecino más cercano altamente eficiente.

El índice vectorial usa una estructura basada en árboles para particionar los datos y facilitar las búsquedas más rápidas. Spanner ofrece configuraciones de árbol de dos y tres niveles:

  • Configuración de árbol de dos niveles: Los nodos hoja (num_leaves) contienen grupos de vectores estrechamente relacionados junto con su centroide correspondiente. El nivel raíz consiste en los centroides de todos los nodos hoja.
  • Configuración de árbol de tres niveles: Es similar en concepto a un árbol de dos niveles, pero introduce una capa de ramas adicional (num_branches), desde la cual los centroides de los nodos de hoja se particionan aún más para formar el nivel raíz (num_leaves).

Spanner elige un índice por ti. Sin embargo, si sabes que un índice específico funciona mejor, puedes usar la sugerencia FORCE_INDEX para elegir el índice vectorial más adecuado para tu caso de uso.

Para obtener más información, consulta las sentencias VECTOR INDEX.

Crea un índice vectorial

Para optimizar la recuperación y el rendimiento de un índice vectorial, te recomendamos que hagas lo siguiente:

  • Crea tu índice de vectores después de que la mayoría de las filas con incorporaciones se escriban en tu base de datos. También es posible que debas volver a compilar el índice de vectores de forma periódica después de insertar datos nuevos. Para obtener más información, consulta Cómo volver a compilar el índice vectorial.

  • Usa la cláusula STORING para almacenar una copia de una columna en el índice vectorial. Si un valor de columna se almacena en el índice vectorial, Spanner realiza el filtrado a nivel de la hoja del índice para mejorar el rendimiento de las consultas. Te recomendamos que almacenes una columna si se usa en una condición de filtrado. Para obtener más información sobre el uso de STORING en un índice, consulta Crea un índice para análisis de solo índice.

Cuando crees tu tabla, la columna de incorporación debe ser un array del tipo de datos FLOAT32 (recomendado) o FLOAT64, y debe tener una anotación vector_length que indique la dimensión de los vectores.

La siguiente sentencia DDL crea una tabla Documents con una columna de inserción DocEmbedding con una longitud de vector:

CREATE TABLE Documents {
  ...
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128);
};

Después de propagar tu tabla Documents, puedes crear un índice vectorial con un árbol de dos niveles y 1,000 nodos hoja en una tabla Documents con una columna de incorporación DocEmbedding usando la distancia cosencial:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING (WordCount)
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

Para crear un índice vectorial con un árbol de tres niveles y 1000000 nodos hoja, haz lo siguiente:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);

Si tu columna de inserción no está marcada como NOT NULL en la definición de la tabla, debes declararla con una cláusula WHERE column_name IS NOT NULL:

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

Consulta embeddings de vectores

Para consultar un índice vectorial, usa una de las tres funciones de distancia aproximadas:

  • APPROX_COSINE_DISTANCE
  • APPROX_EUCLIDEAN_DISTANCE
  • APPROX_DOT_PRODUCT

Entre las restricciones para usar las funciones de distancia aproximada, se incluyen las siguientes:

  • La función de distancia aproximada debe calcular la distancia entre una columna de incorporación y una expresión constante (por ejemplo, un parámetro o un literal).
  • El resultado de la función de distancia aproximada se debe usar en una cláusula ORDER BY como la única clave de ordenamiento, y se debe especificar un LIMIT después de ORDER BY.
  • La consulta debe filtrar de forma explícita las filas que no están indexadas. En la mayoría de los casos, esto significa que la consulta debe incluir una cláusula WHERE <column_name> IS NOT NULL que coincida con la definición del índice vectorial, a menos que la columna ya esté marcada como NOT NULL en la definición de la tabla.

Para obtener una lista detallada de las limitaciones, consulta la página de referencia de la función de distancia aproximada.

Ejemplo

Para buscar los 100 vectores más cercanos a [1.0, 2.0, 3.0], haz lo siguiente:

SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

Si la columna de inserción es anulable, haz lo siguiente:

SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
  ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
  options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100

Prácticas recomendadas

Sigue estas prácticas recomendadas para optimizar tus índices vectoriales y mejorar los resultados de las consultas.

Ajusta las opciones de búsqueda vectorial

El valor de búsqueda de vectores más óptimo depende del caso de uso, del conjunto de datos de vectores y de los vectores de consulta. Es posible que debas realizar un ajuste iterativo para encontrar los mejores valores para tu carga de trabajo específica.

Estos son algunos lineamientos útiles que debes seguir cuando elijas valores adecuados:

  • tree_depth (nivel de árbol): Si la tabla a la que se le indexa tiene menos de 10 millones de filas, usa un tree_depth de 2. De lo contrario, un tree_depth de 3 admite tablas de hasta 10,000 millones de filas.

  • num_leaves: Usa la raíz cuadrada de la cantidad de filas del conjunto de datos. Un valor más alto puede aumentar el tiempo de compilación del índice vectorial. Evita establecer num_leaves más grande que table_row_count/1000, ya que esto genera hojas demasiado pequeñas y un rendimiento deficiente.

  • num_leaves_to_search: Esta opción especifica cuántos nodos hoja del índice se buscan. Aumentar num_leaves_to_search mejora la recuperación, pero también aumenta la latencia y el costo. Recomendamos usar un número que sea el 1% de la cantidad total de hojas definidas en la sentencia CREATE VECTOR INDEX como el valor para num_leaves_to_search. Si usas una cláusula de filtro, aumenta este valor para ampliar la búsqueda.

Si se logra una recuperación aceptable, pero el costo de la consulta es demasiado alto, lo que genera una QPS máxima baja, intenta aumentar num_leaves siguiendo estos pasos:

  1. Establece num_leaves en algún múltiplo de k de su valor original (por ejemplo, 2 * sqrt(table_row_count)).
  2. Establece num_leaves_to_search como el mismo múltiplo k de su valor original.
  3. Experimenta con la reducción de num_leaves_to_search para mejorar el costo y las QPS mientras mantienes la recuperación.

Mejora la recuperación

Existen varias posibilidades de que el recuerdo empeore, incluidas las siguientes:

  • num_leaves_to_search es demasiado pequeño: Es posible que te resulte más difícil encontrar los vecinos más cercanos para algunos vectores de consulta, por lo que aumentar num_leaves_to_search para buscar más hojas puede ayudar a mejorar la recuperación. Es posible que las búsquedas recientes hayan cambiado para incluir más de estos vectores desafiantes.

  • Se debe volver a compilar el índice vectorial: La estructura de árbol del índice vectorial se optimiza para el conjunto de datos en el momento de la creación y es estática a partir de ese momento. Por lo tanto, si se agregan vectores muy diferentes después de crear el índice de vectores inicial, la estructura del árbol podría ser subóptima, lo que generaría una recuperación más deficiente.

Vuelve a compilar el índice vectorial

Para volver a compilar tu índice vectorial sin tiempo de inactividad, sigue estos pasos:

  1. Crea un índice vectorial nuevo en la misma columna de incorporación que el índice vectorial actual y actualiza los parámetros (por ejemplo, OPTIONS) según corresponda.
  2. Una vez que se complete la creación del índice, usa la sugerencia FORCE_INDEX para apuntar al índice nuevo y actualizar la consulta de búsqueda vectorial. Esto garantiza que la consulta use el nuevo índice vectorial. Es posible que también debas volver a ajustar num_leaves_to_search en tu nueva consulta.
  3. Descarta el índice vectorial desactualizado.

¿Qué sigue?