Двоичный вектор
Двоичные векторы - это особая форма представления данных, которая преобразует традиционные высокоразмерные векторы с плавающей точкой в двоичные векторы, содержащие только 0 и 1. Это преобразование не только уменьшает размер вектора, но и снижает затраты на хранение и вычисления, сохраняя при этом семантическую информацию. Когда точность для некритичных характеристик не важна, двоичные векторы могут эффективно сохранять большую часть целостности и полезности исходных векторов с плавающей точкой.
Двоичные векторы имеют широкий спектр применения, особенно в ситуациях, когда эффективность вычислений и оптимизация хранения данных имеют решающее значение. В крупномасштабных системах искусственного интеллекта, таких как поисковые или рекомендательные системы, обработка огромных объемов данных в реальном времени является ключевой задачей. Уменьшая размер векторов, двоичные векторы помогают снизить время ожидания и вычислительные затраты без существенного ущерба для точности. Кроме того, двоичные векторы полезны в средах с ограниченными ресурсами, таких как мобильные устройства и встроенные системы, где память и вычислительная мощность ограничены. Благодаря использованию двоичных векторов сложные функции искусственного интеллекта могут быть реализованы в таких ограниченных условиях при сохранении высокой производительности.
Обзор
Двоичные векторы - это метод кодирования сложных объектов (таких как изображения, текст или аудио) в двоичные значения фиксированной длины. В Milvus двоичные векторы обычно представляются в виде массивов битов или массивов байтов. Например, 8-мерный двоичный вектор может быть представлен как [1, 0, 1, 1, 0, 0, 1, 0].
На диаграмме ниже показано, как двоичные векторы представляют наличие ключевых слов в текстовом контенте. В этом примере для представления двух разных текстов(Текст 1 и Текст 2) используется 10-мерный двоичный вектор, где каждое измерение соответствует слову в словаре: 1 означает присутствие слова в тексте, а 0 - его отсутствие.
Двоичный вектор
Двоичные векторы обладают следующими характеристиками:
Эффективное хранение: Каждое измерение требует всего 1 бит памяти, что значительно сокращает пространство для хранения.
Быстрое вычисление: Сходство между векторами может быть быстро вычислено с помощью побитовых операций, таких как XOR.
Фиксированная длина: Длина вектора остается неизменной независимо от длины исходного текста, что упрощает индексирование и поиск.
Простота и интуитивность: Непосредственно отражает наличие ключевых слов, что делает его подходящим для некоторых специализированных поисковых задач.
Бинарные векторы могут быть сгенерированы различными методами. При обработке текстов можно использовать предопределенные словари для установки соответствующих битов на основе наличия слов. При обработке изображений алгоритмы перцептивного хэширования (например, pHash) могут генерировать двоичные характеристики изображений. В приложениях машинного обучения выходные данные моделей могут быть бинаризованы для получения двоичных векторных представлений.
После бинарной векторизации данные могут быть сохранены в Milvus для управления и поиска векторов. На диаграмме ниже показан основной процесс.
Использование бинарного вектора
Хотя двоичные векторы отлично подходят для определенных сценариев, их выразительные возможности ограничены, что затрудняет передачу сложных семантических отношений. Поэтому в реальных сценариях двоичные векторы часто используются вместе с другими типами векторов, чтобы сбалансировать эффективность и выразительность. Дополнительные сведения см. в разделах "Плотный вектор" и "Разреженный вектор".
Использование двоичных векторов
Добавить векторное поле
Чтобы использовать двоичные векторы в Milvus, сначала определите векторное поле для хранения двоичных векторов при создании коллекции. Этот процесс включает в себя:
Установка
datatypeв поддерживаемый тип данных двоичного вектора, т. е.BINARY_VECTOR.Указание размеров вектора с помощью параметра
dim. Обратите внимание, чтоdimдолжен быть кратен 8, так как при вставке двоичные векторы должны быть преобразованы в массив байтов. Каждые 8 булевых значений (0 или 1) будут упакованы в 1 байт. Например, еслиdim=128, то для вставки потребуется 16-байтовый массив.
from pymilvus import MilvusClient, DataType
client = MilvusClient(uri="http://localhost:19530")
schema = client.create_schema(
auto_id=True,
enable_dynamic_fields=True,
)
schema.add_field(field_name="pk", datatype=DataType.VARCHAR, is_primary=True, max_length=100)
schema.add_field(field_name="binary_vector", datatype=DataType.BINARY_VECTOR, dim=128)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.build());
CreateCollectionReq.CollectionSchema schema = client.createSchema();
schema.setEnableDynamicField(true);
schema.addField(AddFieldReq.builder()
.fieldName("pk")
.dataType(DataType.VarChar)
.isPrimaryKey(true)
.autoID(true)
.maxLength(100)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("binary_vector")
.dataType(DataType.BinaryVector)
.dimension(128)
.build());
import { DataType } from "@zilliz/milvus2-sdk-node";
schema.push({
name: "binary vector",
data_type: DataType.BinaryVector,
dim: 128,
});
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/column"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/index"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: milvusAddr,
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
schema := entity.NewSchema()
schema.WithField(entity.NewField().
WithName("pk").
WithDataType(entity.FieldTypeVarChar).
WithIsAutoID(true).
WithIsPrimaryKey(true).
WithMaxLength(100),
).WithField(entity.NewField().
WithName("binary_vector").
WithDataType(entity.FieldTypeBinaryVector).
WithDim(128),
)
export primaryField='{
"fieldName": "pk",
"dataType": "VarChar",
"isPrimary": true,
"elementTypeParams": {
"max_length": 100
}
}'
export vectorField='{
"fieldName": "binary_vector",
"dataType": "BinaryVector",
"elementTypeParams": {
"dim": 128
}
}'
export schema="{
\"autoID\": true,
\"fields\": [
$primaryField,
$vectorField
],
\"enableDynamicField\": true
}"
В этом примере добавлено векторное поле с именем binary_vector для хранения двоичных векторов. Тип данных этого поля - BINARY_VECTOR, размерность - 128.
Установка параметров индекса для векторного поля
Для ускорения поиска необходимо создать индекс для двоичного векторного поля. Индексирование может значительно повысить эффективность поиска в больших векторных данных.
index_params = client.prepare_index_params()
index_params.add_index(
field_name="binary_vector",
index_name="binary_vector_index",
index_type="AUTOINDEX",
metric_type="HAMMING"
)
import io.milvus.v2.common.IndexParam;
import java.util.*;
List<IndexParam> indexParams = new ArrayList<>();
Map<String,Object> extraParams = new HashMap<>();
indexParams.add(IndexParam.builder()
.fieldName("binary_vector")
.indexType(IndexParam.IndexType.AUTOINDEX)
.metricType(IndexParam.MetricType.HAMMING)
.build());
import { MetricType, IndexType } from "@zilliz/milvus2-sdk-node";
const indexParams = {
indexName: "binary_vector_index",
field_name: "binary_vector",
metric_type: MetricType.HAMMING,
index_type: IndexType.AUTOINDEX
};
idx := index.NewAutoIndex(entity.HAMMING)
indexOption := milvusclient.NewCreateIndexOption("my_collection", "binary_vector", idx)
export indexParams='[
{
"fieldName": "binary_vector",
"metricType": "HAMMING",
"indexName": "binary_vector_index",
"indexType": "AUTOINDEX"
}
]'
В приведенном выше примере для поля binary_vector создается индекс с именем binary_vector_index, использующий тип индекса AUTOINDEX. Значение metric_type установлено в HAMMING, что указывает на использование расстояния Хэмминга для измерения сходства.
Milvus предоставляет различные типы индексов для более удобного векторного поиска. AUTOINDEX - это специальный тип индекса, предназначенный для сглаживания кривой обучения векторному поиску. Существует множество типов индексов, из которых вы можете выбирать. Для получения подробной информации обратитесь к разделу "Объяснение индексов".
Кроме того, Milvus поддерживает другие метрики сходства для бинарных векторов. Для получения дополнительной информации обратитесь к разделу "Типы метрик".
Создание коллекции
После того как настройки бинарных векторов и индексов завершены, создайте коллекцию, содержащую бинарные векторы. В примере ниже используется метод create_collection для создания коллекции с именем my_collection.
client.create_collection(
collection_name="my_collection",
schema=schema,
index_params=index_params
)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.build());
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName("my_collection")
.collectionSchema(schema)
.indexParams(indexParams)
.build();
client.createCollection(requestCreate);
import { MilvusClient } from "@zilliz/milvus2-sdk-node";
const client = new MilvusClient({
address: 'http://localhost:19530'
});
await client.createCollection({
collection_name: 'my_collection',
schema: schema,
index_params: indexParams
});
err = client.CreateCollection(ctx,
milvusclient.NewCreateCollectionOption("my_collection", schema).
WithIndexOptions(indexOption))
if err != nil {
fmt.Println(err.Error())
// handle error
}
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_collection\",
\"schema\": $schema,
\"indexParams\": $indexParams
}"
Вставка данных
После создания коллекции используйте метод insert для добавления данных, содержащих двоичные векторы. Обратите внимание, что двоичные векторы должны быть представлены в виде байтового массива, где каждый байт представляет собой 8 булевых значений.
Например, для 128-мерного двоичного вектора требуется 16-байтовый массив (так как 128 бит ÷ 8 бит/байт = 16 байт). Ниже приведен пример кода для вставки данных:
def convert_bool_list_to_bytes(bool_list):
if len(bool_list) % 8 != 0:
raise ValueError("The length of a boolean list must be a multiple of 8")
byte_array = bytearray(len(bool_list) // 8)
for i, bit in enumerate(bool_list):
if bit == 1:
index = i // 8
shift = i % 8
byte_array[index] |= (1 << shift)
return bytes(byte_array)
bool_vectors = [
[1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0] + [0] * 112,
[0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1] + [0] * 112,
]
data = [{"binary_vector": convert_bool_list_to_bytes(bool_vector) for bool_vector in bool_vectors}]
client.insert(
collection_name="my_collection",
data=data
)
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;
private static byte[] convertBoolArrayToBytes(boolean[] booleanArray) {
byte[] byteArray = new byte[booleanArray.length / Byte.SIZE];
for (int i = 0; i < booleanArray.length; i++) {
if (booleanArray[i]) {
int index = i / Byte.SIZE;
int shift = i % Byte.SIZE;
byteArray[index] |= (byte) (1 << shift);
}
}
return byteArray;
}
List<JsonObject> rows = new ArrayList<>();
Gson gson = new Gson();
{
boolean[] boolArray = {true, false, false, true, true, false, true, true, false, true, false, false, true, true, false, true};
JsonObject row = new JsonObject();
row.add("binary_vector", gson.toJsonTree(convertBoolArrayToBytes(boolArray)));
rows.add(row);
}
{
boolean[] boolArray = {false, true, false, true, false, true, false, false, true, true, false, false, true, true, false, true};
JsonObject row = new JsonObject();
row.add("binary_vector", gson.toJsonTree(convertBoolArrayToBytes(boolArray)));
rows.add(row);
}
InsertResp insertR = client.insert(InsertReq.builder()
.collectionName("my_collection")
.data(rows)
.build());
const data = [
{ binary_vector: [1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1] },
{ binary_vector: [1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1] },
];
client.insert({
collection_name: "my_collection",
data: data,
});
_, err = client.Insert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
WithBinaryVectorColumn("binary_vector", 128, [][]byte{
{0b10011011, 0b01010100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0b10011011, 0b01010101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}))
if err != nil {
fmt.Println(err.Error())
// handle err
}
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"data\": $data,
\"collectionName\": \"my_collection\"
}"
Выполнение поиска по сходству
Поиск по сходству - одна из основных функций Milvus, позволяющая быстро находить данные, наиболее похожие на вектор запроса, на основе расстояния между векторами. Чтобы выполнить поиск по сходству с использованием бинарных векторов, подготовьте вектор запроса и параметры поиска, а затем вызовите метод search.
В процессе поиска бинарные векторы также должны быть предоставлены в виде массива байтов. Убедитесь, что размерность вектора запроса соответствует размерности, указанной при определении dim, и что каждые 8 булевых значений преобразуются в 1 байт.
search_params = {
"params": {"nprobe": 10}
}
query_bool_list = [1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0] + [0] * 112
query_vector = convert_bool_list_to_bytes(query_bool_list)
res = client.search(
collection_name="my_collection",
data=[query_vector],
anns_field="binary_vector",
search_params=search_params,
limit=5,
output_fields=["pk"]
)
print(res)
# Output
# data: ["[{'id': '453718927992172268', 'distance': 10.0, 'entity': {'pk': '453718927992172268'}}]"]
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.BinaryVec;
import io.milvus.v2.service.vector.response.SearchResp;
Map<String,Object> searchParams = new HashMap<>();
searchParams.put("nprobe",10);
boolean[] boolArray = {true, false, false, true, true, false, true, true, false, true, false, false, true, true, false, true};
BinaryVec queryVector = new BinaryVec(convertBoolArrayToBytes(boolArray));
SearchResp searchR = client.search(SearchReq.builder()
.collectionName("my_collection")
.data(Collections.singletonList(queryVector))
.annsField("binary_vector")
.searchParams(searchParams)
.topK(5)
.outputFields(Collections.singletonList("pk"))
.build());
System.out.println(searchR.getSearchResults());
// Output
//
// [[SearchResp.SearchResult(entity={pk=453444327741536775}, score=0.0, id=453444327741536775), SearchResp.SearchResult(entity={pk=453444327741536776}, score=7.0, id=453444327741536776)]]
query_vector = [1,0,1,0,1,1,1,1,1,1,1,1];
client.search({
collection_name: 'my_collection',
data: query_vector,
limit: 5,
output_fields: ['pk'],
params: {
nprobe: 10
}
});
queryVector := []byte{0b10011011, 0b01010100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
annSearchParams := index.NewCustomAnnParam()
annSearchParams.WithExtraParam("nprobe", 10)
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"my_collection", // collectionName
5, // limit
[]entity.Vector{entity.BinaryVector(queryVector)},
).WithANNSField("binary_vector").
WithOutputFields("pk").
WithAnnParam(annSearchParams))
if err != nil {
fmt.Println(err.Error())
// handle err
}
for _, resultSet := range resultSets {
fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
fmt.Println("Scores: ", resultSet.Scores)
fmt.Println("Pks: ", resultSet.GetColumn("pk").FieldData().GetScalars())
}
export searchParams='{
"params":{"nprobe":10}
}'
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_collection\",
\"data\": $data,
\"annsField\": \"binary_vector\",
\"limit\": 5,
\"searchParams\":$searchParams,
\"outputFields\": [\"pk\"]
}"
Дополнительную информацию о параметрах поиска сходства см. в разделе Базовый поиск ANN.