В последние годы с развитием технологий глубокого обучения векторный поиск привлек всеобщее внимание. Еще в версии Elasticsearch 7.2.0 был введен тип поля Densent_vector для поддержки хранения многомерных векторных данных, таких как встраивания слов или встраивания документов, для таких операций, как поиск по сходству. В этой статье я покажу, как использовать Densent_vector для векторного поиска в версии Elasticsearch 8.X.
Во-первых, нам нужно понять Density_vector. Density_vector — это тип поля, используемый Elasticsearch для хранения многомерных векторов, часто используемый в нейронном поиске для поиска похожего текста с использованием внедрений, созданных с помощью НЛП и моделей глубокого обучения. Более подробную информацию о плотном_векторе вы можете найти по этой ссылке.
В следующем разделе я покажу, как создать простой индекс Elasticsearch, содержащий возможности векторного поиска на основе встраивания текста.
Во-первых, нам нужно сгенерировать встраивание текста, используя Python и модель BERT. Вот пример того, как мы это делаем:
import torch
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")
def get_bert_embedding(text):
inputs = tokenizer(text, return_tensors="pt", max_length=128, truncation=True, padding="max_length")
with torch.no_grad():
outputs = model(**inputs)
return outputs.last_hidden_state[:, :3, :].numpy()
def print_infos():
docs = ["Город барбекю, занимающий площадь в 100 акров, был успешно построен в Цзыбо всего за 20 дней и теперь стал популярным местом, где тысячи людей соревнуются за "места для барбекю".",
«Новый барбекю-город площадью 100 акров в Цзыбо был построен всего за 20 дней, что привлекло множество любителей барбекю. В настоящее время трудно найти «места для барбекю».»,
«В Цзыбо город барбекю площадью 100 акров, построенный за 20 дней, стал центром всеобщего внимания. Тысячи людей соперничают за различные вкусные барбекю, которые трудно найти».
«Цзыбо обычно относится к городу Цзыбо. Город Цзыбо, называемый «Цзы», является древней столицей государства Ци, городом префектурного уровня, находящимся под юрисдикцией провинции Шаньдун, и крупным городом типа II»]
for doc in docs:
print( f"Vector for '{doc}':", get_bert_embedding( doc ) )
if __name__ == '__main__':
print_infos()
В приведенном выше сценарии мы определяем функцию get_bert_embedding для создания векторного представления каждого документа. Затем мы сгенерировали четыре разных вектора документа и вывели их выходные данные на консоль. Как показано ниже:
Ссылка на результат:
Vector for «Город барбекю», занимающий площадь в 100 акров, был успешно построен в Цзыбо всего за 20 дней и теперь стал популярным местом для тысяч людей, состязающихся за «места для барбекю». ': [[[-0.2703271 0.38279012 -0.29274252 ... -0.24937081 0.7212287
0.0751707 ]
[ 0.01726123 0.1450473 0.16286954 ... -0.20245396 1.1556625
-0.112049 ]
[ 0.51697373 -0.01454506 0.1063835 ... -0.2986216 0.69151103
0.13124703]]]
Vector for «Новый город для барбекю площадью 100 акров в Цзыбо был построен всего за 20 дней, что привлекло множество любителей барбекю. В настоящее время трудно найти «места для барбекю». ': [[[-0.22879271 0.43286988 -0.21742335 ... -0.22972387 0.75263715
0.03716223]
[ 0.1252176 -0.02892866 0.17054333 ... -0.30524847 0.94903445
-0.46865308]
[ 0.42650488 0.34019586 -0.01442122 ... -0.17345914 0.6688627
-0.75012964]]]
Сначала нам нужно создать новый индекс в Elasticsearch для хранения наших документов и их векторных представлений. Вот вызовы API для создания индекса:
PUT /my_vector_index
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"content_vector": {
"type": "dense_vector",
"dims": 3
}
}
}
}
В приведенном выше коде мы создаем индекс с именем my_vector_index и определяем два поля: title и content_vector. Среди них тип поля content_vector установлен на Density_vector, а его размерность указана как 3, что соответствует размерности вектора BERT, который мы сгенерировали ранее.
Далее мы можем импортировать наши документы и соответствующие им векторы в индекс. Ниже приведен пример вызова API массового импорта:
POST my_vector_index/_bulk
{"index":{"_id":1}}
{"title":"Город барбекю, занимающий площадь в 100 акров, был успешно построен в Цзыбо всего за 20 дней и теперь стал популярным местом для тысяч людей, соревнующихся за "места для барбекю."","content_vector" :[-0.2703271, 0.38279012, -0.29274252]}
{"index":{"_id":2}}
{"title":«Новый барбекю-город площадью 100 акров в Цзыбо был построен всего за 20 дней, что привлекло множество любителей барбекю. В настоящее время трудно найти «места для барбекю».»,"content_vector":[-0.22879271, 0.43286988, -0.21742335]}
{"index":{"_id":3}}
{"title":«В Цзыбо город барбекю площадью 100 акров, построенный за 20 дней, стал центром всеобщего внимания. Тысячи людей соперничают за различные вкусные барбекю, которые трудно найти»."content_vector":[-0.24912262, 0.40769795, -0.26663426]}
{"index":{"_id":4}}
{"title":«Цзыбо обычно относится к городу Цзыбо. Город Цзыбо, называемый «Цзы», является древней столицей государства Ци, городом префектурного уровня, находящимся под юрисдикцией провинции Шаньдун, и крупным городом типа II","content_vector":["0.32247472, 0.19048998, -0.36749798]}
В этом примере мы используем интерфейс _bulk Elasticsearch для пакетного импорта данных. Данные для каждого документа состоят из двух строк: одна строка содержит идентификатор документа, а другая строка содержит векторы заголовка и содержимого документа. Обратите внимание, что значения вектора такие же, как те, которые мы сгенерировали в коде Python.
После создания и импорта данных мы можем выполнить поиск по сходству. Мы будем использовать сценарий для оценки запроса, где наш сценарий оценки будет вычислять косинусное сходство между вектором запроса и вектором содержимого каждого документа.
Ниже приведен пример вызова API:
GET my_vector_index/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"source": "cosineSimilarity(params.query_vector, 'content_vector') + 1.0",
"params": {
"query_vector": [-0.2703271, 0.38279012, -0.29274252]
}
}
}
}
}
В приведенном выше запросе мы определили запрос оценки сценария script_score. Этот запрос сначала выполняет запрос, который соответствует всем документам (match_all), а затем оценивает каждый документ на основе нашего сценария.
Сценарий оценки cosineSimilarity(params.query_vector, 'content_vector') + 1.0 вычисляет косинусное сходство между вектором запроса и полем content_vector каждого документа и добавляет 1 к результату (поскольку косинусное сходство находится в диапазоне от -1 до 1, а Elasticsearch оценивает должно быть неотрицательным).
В качестве условия поиска возьмем вектор документа 1, и результаты выполнения будут следующими:
Методы векторного поиска постоянно развиваются, и Elasticsearch постоянно совершенствует и расширяет свои возможности, чтобы идти в ногу с этой тенденцией.
Чтобы в полной мере воспользоваться возможностями Elasticsearch, обязательно следите за его официальной документацией и обновлениями, чтобы быть в курсе новейших функций и лучших практик. Используя поле Densent_vector и связанные с ним методы поиска, мы можем реализовать сложный векторный поиск в Elasticsearch, предоставляя пользователям более точный и персонализированный поиск.