- k-nearest neighbor (kNN) search
- Prerequisites
- kNN methods
- Approximate kNN
- Tune approximate kNN for speed or accuracy
- Approximate kNN using byte vectors
- Byte quantized kNN search
- Filtered kNN search
- Approximate kNN search and filtering
- Combine approximate kNN with other features
- Perform semantic search
- Search multiple kNN fields
- Search kNN with expected similarity
- Nested kNN Search
- Nested kNN Search with Inner hits
- Indexing considerations
- Limitations for approximate kNN search
- Exact kNN
k-nearest neighbor (kNN) search
k-nearest neighbor (kNN) 検索は、類似度メトリックによって測定されたクエリベクトルに最も近い k ベクトルを見つけます。
kNNの一般的な使用例には、次のものが含まれます:
- 自然言語処理 (NLP) アルゴリズムに基づく関連性ランキング
- 製品推奨および推薦エンジン
- 画像や動画の類似検索
Prerequisites
- kNN 検索を実行するには、データを意味のあるベクトル値に変換できる必要があります。これらのベクトルは、Elasticsearch の自然言語処理 (NLP) モデルを使用して作成することができます、または Elasticsearch の外部で生成することができます。ベクトルは、
dense_vector
フィールド値としてドキュメントに追加できます。クエリは、同じ次元のベクトルとして表されます。
ドキュメントのベクトルがクエリベクトルに近いほど、類似度メトリックに基づいてマッチが良くなるようにベクトルを設計してください。 - このガイドの手順を完了するには、次の インデックス権限 が必要です:
create_index
またはmanage
を使用してdense_vector
フィールドを持つインデックスを作成するcreate
、index
、またはwrite
を使用して作成したインデックスにデータを追加するread
を使用してインデックスを検索する
kNN methods
Elasticsearch は kNN 検索のための 2 つのメソッドをサポートしています:
- Approximate kNN は
knn
検索オプションまたはknn
クエリを使用 - Exact, brute-force kNN はベクトル関数を使用した
script_score
クエリを使用
ほとんどの場合、近似 kNN を使用することをお勧めします。近似 kNN は、インデックス作成が遅く、精度が不完全である代わりに、低遅延を提供します。
正確なブルートフォース kNN は正確な結果を保証しますが、大規模データセットではスケールしません。このアプローチでは、script_score
クエリが各一致するドキュメントをスキャンしてベクトル関数を計算する必要があり、検索速度が遅くなる可能性があります。ただし、クエリ を使用して関数に渡される一致するドキュメントの数を制限することで、遅延を改善できます。データを小さなサブセットのドキュメントにフィルタリングすると、このアプローチを使用して良好な検索パフォーマンスを得ることができます。
Approximate kNN
他のタイプの検索と比較して、近似 kNN 検索には特定のリソース要件があります。特に、すべてのベクトルデータは、効率的にするためにノードのページキャッシュに収まる必要があります。構成とサイズに関する重要な注意事項については、近似 kNN 検索の調整ガイド を参照してください。
近似 kNN 検索を実行するには、knn
オプションを使用して、インデックス作成が有効な 1 つ以上の dense_vector
フィールドを検索します。
- 1. 1 つ以上の
dense_vector
フィールドを明示的にマッピングします。近似 kNN 検索には、次のマッピングオプションが必要です:similarity
値。この値は、クエリとドキュメントベクトルの類似性に基づいてドキュメントをスコアリングするために使用される類似度メトリックを決定します。利用可能なメトリックのリストについては、similarity
パラメータのドキュメントを参照してください。similarity
設定はcosine
にデフォルト設定されています。
Python
resp = client.indices.create(
index="image-index",
mappings={
"properties": {
"image-vector": {
"type": "dense_vector",
"dims": 3,
"similarity": "l2_norm"
},
"title-vector": {
"type": "dense_vector",
"dims": 5,
"similarity": "l2_norm"
},
"title": {
"type": "text"
},
"file-type": {
"type": "keyword"
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'image-index',
body: {
mappings: {
properties: {
"image-vector": {
type: 'dense_vector',
dims: 3,
similarity: 'l2_norm'
},
"title-vector": {
type: 'dense_vector',
dims: 5,
similarity: 'l2_norm'
},
title: {
type: 'text'
},
"file-type": {
type: 'keyword'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "image-index",
mappings: {
properties: {
"image-vector": {
type: "dense_vector",
dims: 3,
similarity: "l2_norm",
},
"title-vector": {
type: "dense_vector",
dims: 5,
similarity: "l2_norm",
},
title: {
type: "text",
},
"file-type": {
type: "keyword",
},
},
},
});
console.log(response);
Console
PUT image-index
{
"mappings": {
"properties": {
"image-vector": {
"type": "dense_vector",
"dims": 3,
"similarity": "l2_norm"
},
"title-vector": {
"type": "dense_vector",
"dims": 5,
"similarity": "l2_norm"
},
"title": {
"type": "text"
},
"file-type": {
"type": "keyword"
}
}
}
}
Console
POST image-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "image-vector": [1, 5, -20], "title-vector": [12, 50, -10, 0, 1], "title": "moose family", "file-type": "jpg" }
{ "index": { "_id": "2" } }
{ "image-vector": [42, 8, -15], "title-vector": [25, 1, 4, -12, 2], "title": "alpine lake", "file-type": "png" }
{ "index": { "_id": "3" } }
{ "image-vector": [15, 11, 23], "title-vector": [1, 5, 25, 50, 20], "title": "full moon", "file-type": "jpg" }
...
Python
resp = client.search(
index="image-index",
knn={
"field": "image-vector",
"query_vector": [
-5,
9,
-12
],
"k": 10,
"num_candidates": 100
},
fields=[
"title",
"file-type"
],
)
print(resp)
Ruby
response = client.search(
index: 'image-index',
body: {
knn: {
field: 'image-vector',
query_vector: [
-5,
9,
-12
],
k: 10,
num_candidates: 100
},
fields: [
'title',
'file-type'
]
}
)
puts response
Js
const response = await client.search({
index: "image-index",
knn: {
field: "image-vector",
query_vector: [-5, 9, -12],
k: 10,
num_candidates: 100,
},
fields: ["title", "file-type"],
});
console.log(response);
Console
POST image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [-5, 9, -12],
"k": 10,
"num_candidates": 100
},
"fields": [ "title", "file-type" ]
}
ドキュメント _score
は、クエリとドキュメントベクトルの類似性によって決定されます。kNN 検索のスコアがどのように計算されるかについての詳細は、similarity
を参照してください。
近似 kNN 検索のサポートはバージョン 8.0 で追加されました。それ以前は、dense_vector
フィールドはマッピングで index
を有効にすることをサポートしていませんでした。8.0 より前に dense_vector
フィールドを含むインデックスを作成した場合、近似 kNN 検索をサポートするには、index: true
を設定する新しいフィールドマッピングを使用してデータを再インデックス化する必要があります。これはデフォルトオプションです。
Tune approximate kNN for speed or accuracy
結果を収集するために、kNN 検索 API は各シャードで num_candidates
数の近似最近傍候補を見つけます。検索は、これらの候補ベクトルとクエリベクトルの類似性を計算し、各シャードから最も類似した k
結果を選択します。検索は、各シャードからの結果をマージして、グローバルなトップ k
最近傍を返します。
num_candidates
を増やすことで、検索速度が遅くなる代わりに、より正確な結果を得ることができます。num_candidates
の高い値を持つ検索は、各シャードからより多くの候補を考慮します。これには時間がかかりますが、検索は真の k
最近傍を見つける可能性が高くなります。
同様に、num_candidates
を減少させることで、潜在的に精度が低下する可能性のあるより速い検索を実現できます。
Approximate kNN using byte vectors
近似 kNN 検索 API は、byte
値ベクトルに加えて float
値ベクトルをサポートしています。knn
オプション を使用して、dense_vector
フィールド を byte
に設定し、インデックス作成を有効にして検索します。
- 1.
element_type
をbyte
に設定し、インデックス作成を有効にして、1 つ以上のdense_vector
フィールドを明示的にマッピングします。
Python
resp = client.indices.create(
index="byte-image-index",
mappings={
"properties": {
"byte-image-vector": {
"type": "dense_vector",
"element_type": "byte",
"dims": 2
},
"title": {
"type": "text"
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'byte-image-index',
body: {
mappings: {
properties: {
"byte-image-vector": {
type: 'dense_vector',
element_type: 'byte',
dims: 2
},
title: {
type: 'text'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "byte-image-index",
mappings: {
properties: {
"byte-image-vector": {
type: "dense_vector",
element_type: "byte",
dims: 2,
},
title: {
type: "text",
},
},
},
});
console.log(response);
Console
PUT byte-image-index
{
"mappings": {
"properties": {
"byte-image-vector": {
"type": "dense_vector",
"element_type": "byte",
"dims": 2
},
"title": {
"type": "text"
}
}
}
}
- 2. データをインデックスし、すべてのベクトル値が [-128, 127] の範囲内の整数であることを確認します。
Python
resp = client.bulk(
index="byte-image-index",
refresh=True,
operations=[
{
"index": {
"_id": "1"
}
},
{
"byte-image-vector": [
5,
-20
],
"title": "moose family"
},
{
"index": {
"_id": "2"
}
},
{
"byte-image-vector": [
8,
-15
],
"title": "alpine lake"
},
{
"index": {
"_id": "3"
}
},
{
"byte-image-vector": [
11,
23
],
"title": "full moon"
}
],
)
print(resp)
Ruby
response = client.bulk(
index: 'byte-image-index',
refresh: true,
body: [
{
index: {
_id: '1'
}
},
{
"byte-image-vector": [
5,
-20
],
title: 'moose family'
},
{
index: {
_id: '2'
}
},
{
"byte-image-vector": [
8,
-15
],
title: 'alpine lake'
},
{
index: {
_id: '3'
}
},
{
"byte-image-vector": [
11,
23
],
title: 'full moon'
}
]
)
puts response
Js
const response = await client.bulk({
index: "byte-image-index",
refresh: "true",
operations: [
{
index: {
_id: "1",
},
},
{
"byte-image-vector": [5, -20],
title: "moose family",
},
{
index: {
_id: "2",
},
},
{
"byte-image-vector": [8, -15],
title: "alpine lake",
},
{
index: {
_id: "3",
},
},
{
"byte-image-vector": [11, 23],
title: "full moon",
},
],
});
console.log(response);
Console
POST byte-image-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "byte-image-vector": [5, -20], "title": "moose family" }
{ "index": { "_id": "2" } }
{ "byte-image-vector": [8, -15], "title": "alpine lake" }
{ "index": { "_id": "3" } }
{ "byte-image-vector": [11, 23], "title": "full moon" }
- 3.
knn
オプション を使用して検索を実行し、query_vector
値が [-128, 127] の範囲内の整数であることを確認します。
Python
resp = client.search(
index="byte-image-index",
knn={
"field": "byte-image-vector",
"query_vector": [
-5,
9
],
"k": 10,
"num_candidates": 100
},
fields=[
"title"
],
)
print(resp)
Ruby
response = client.search(
index: 'byte-image-index',
body: {
knn: {
field: 'byte-image-vector',
query_vector: [
-5,
9
],
k: 10,
num_candidates: 100
},
fields: [
'title'
]
}
)
puts response
Js
const response = await client.search({
index: "byte-image-index",
knn: {
field: "byte-image-vector",
query_vector: [-5, 9],
k: 10,
num_candidates: 100,
},
fields: ["title"],
});
console.log(response);
Console
POST byte-image-index/_search
{
"knn": {
"field": "byte-image-vector",
"query_vector": [-5, 9],
"k": 10,
"num_candidates": 100
},
"fields": [ "title" ]
}
注: 標準のバイト配列に加えて、query_vector
パラメータに対して16進エンコードされた文字列値を提供することもできます。たとえば、上記の検索リクエストは、次のように表現することもでき、同じ結果が得られます。
Python
resp = client.search(
index="byte-image-index",
knn={
"field": "byte-image-vector",
"query_vector": "fb09",
"k": 10,
"num_candidates": 100
},
fields=[
"title"
],
)
print(resp)
Ruby
response = client.search(
index: 'byte-image-index',
body: {
knn: {
field: 'byte-image-vector',
query_vector: 'fb09',
k: 10,
num_candidates: 100
},
fields: [
'title'
]
}
)
puts response
Js
const response = await client.search({
index: "byte-image-index",
knn: {
field: "byte-image-vector",
query_vector: "fb09",
k: 10,
num_candidates: 100,
},
fields: ["title"],
});
console.log(response);
Console
POST byte-image-index/_search
{
"knn": {
"field": "byte-image-vector",
"query_vector": "fb09",
"k": 10,
"num_candidates": 100
},
"fields": [ "title" ]
}
Byte quantized kNN search
float
ベクトルを提供したいが、byte
ベクトルのメモリ節約を望む場合は、量子化 機能を使用できます。量子化を使用すると、float
ベクトルを提供できますが、内部的には byte
ベクトルとしてインデックスされます。さらに、元の float
ベクトルはインデックスに保持されます。
dense_vector
のデフォルトインデックスタイプは int8_hnsw
です。
量子化を使用するには、int8_hnsw
または int4_hnsw
オブジェクトを dense_vector
マッピングで使用できます。
Python
resp = client.indices.create(
index="quantized-image-index",
mappings={
"properties": {
"image-vector": {
"type": "dense_vector",
"element_type": "float",
"dims": 2,
"index": True,
"index_options": {
"type": "int8_hnsw"
}
},
"title": {
"type": "text"
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'quantized-image-index',
body: {
mappings: {
properties: {
"image-vector": {
type: 'dense_vector',
element_type: 'float',
dims: 2,
index: true,
index_options: {
type: 'int8_hnsw'
}
},
title: {
type: 'text'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "quantized-image-index",
mappings: {
properties: {
"image-vector": {
type: "dense_vector",
element_type: "float",
dims: 2,
index: true,
index_options: {
type: "int8_hnsw",
},
},
title: {
type: "text",
},
},
},
});
console.log(response);
Console
PUT quantized-image-index
{
"mappings": {
"properties": {
"image-vector": {
"type": "dense_vector",
"element_type": "float",
"dims": 2,
"index": true,
"index_options": {
"type": "int8_hnsw"
}
},
"title": {
"type": "text"
}
}
}
}
- 1.
float
ベクトルをインデックスします。
Python
resp = client.bulk(
index="quantized-image-index",
refresh=True,
operations=[
{
"index": {
"_id": "1"
}
},
{
"image-vector": [
0.1,
-2
],
"title": "moose family"
},
{
"index": {
"_id": "2"
}
},
{
"image-vector": [
0.75,
-1
],
"title": "alpine lake"
},
{
"index": {
"_id": "3"
}
},
{
"image-vector": [
1.2,
0.1
],
"title": "full moon"
}
],
)
print(resp)
Ruby
response = client.bulk(
index: 'quantized-image-index',
refresh: true,
body: [
{
index: {
_id: '1'
}
},
{
"image-vector": [
0.1,
-2
],
title: 'moose family'
},
{
index: {
_id: '2'
}
},
{
"image-vector": [
0.75,
-1
],
title: 'alpine lake'
},
{
index: {
_id: '3'
}
},
{
"image-vector": [
1.2,
0.1
],
title: 'full moon'
}
]
)
puts response
Js
const response = await client.bulk({
index: "quantized-image-index",
refresh: "true",
operations: [
{
index: {
_id: "1",
},
},
{
"image-vector": [0.1, -2],
title: "moose family",
},
{
index: {
_id: "2",
},
},
{
"image-vector": [0.75, -1],
title: "alpine lake",
},
{
index: {
_id: "3",
},
},
{
"image-vector": [1.2, 0.1],
title: "full moon",
},
],
});
console.log(response);
Console
POST quantized-image-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "image-vector": [0.1, -2], "title": "moose family" }
{ "index": { "_id": "2" } }
{ "image-vector": [0.75, -1], "title": "alpine lake" }
{ "index": { "_id": "3" } }
{ "image-vector": [1.2, 0.1], "title": "full moon" }
- 2.
knn
オプション を使用して検索を実行します。検索時に、float
ベクトルは自動的にbyte
ベクトルに量子化されます。
Python
resp = client.search(
index="quantized-image-index",
knn={
"field": "image-vector",
"query_vector": [
0.1,
-2
],
"k": 10,
"num_candidates": 100
},
fields=[
"title"
],
)
print(resp)
Ruby
response = client.search(
index: 'quantized-image-index',
body: {
knn: {
field: 'image-vector',
query_vector: [
0.1,
-2
],
k: 10,
num_candidates: 100
},
fields: [
'title'
]
}
)
puts response
Js
const response = await client.search({
index: "quantized-image-index",
knn: {
field: "image-vector",
query_vector: [0.1, -2],
k: 10,
num_candidates: 100,
},
fields: ["title"],
});
console.log(response);
Console
POST quantized-image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [0.1, -2],
"k": 10,
"num_candidates": 100
},
"fields": [ "title" ]
}
元の float
ベクトルはインデックスに保持されているため、再スコアリングにオプションで使用できます。つまり、int8_hnsw
インデックスを使用してすべてのベクトルを迅速に検索し、トップ k
結果のみを再スコアリングできます。これにより、迅速な検索と正確なスコアリングの両方を実現できます。
Python
resp = client.search(
index="quantized-image-index",
knn={
"field": "image-vector",
"query_vector": [
0.1,
-2
],
"k": 15,
"num_candidates": 100
},
fields=[
"title"
],
rescore={
"window_size": 10,
"query": {
"rescore_query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"source": "cosineSimilarity(params.query_vector, 'image-vector') + 1.0",
"params": {
"query_vector": [
0.1,
-2
]
}
}
}
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'quantized-image-index',
body: {
knn: {
field: 'image-vector',
query_vector: [
0.1,
-2
],
k: 15,
num_candidates: 100
},
fields: [
'title'
],
rescore: {
window_size: 10,
query: {
rescore_query: {
script_score: {
query: {
match_all: {}
},
script: {
source: "cosineSimilarity(params.query_vector, 'image-vector') + 1.0",
params: {
query_vector: [
0.1,
-2
]
}
}
}
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "quantized-image-index",
knn: {
field: "image-vector",
query_vector: [0.1, -2],
k: 15,
num_candidates: 100,
},
fields: ["title"],
rescore: {
window_size: 10,
query: {
rescore_query: {
script_score: {
query: {
match_all: {},
},
script: {
source:
"cosineSimilarity(params.query_vector, 'image-vector') + 1.0",
params: {
query_vector: [0.1, -2],
},
},
},
},
},
},
});
console.log(response);
Console
POST quantized-image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [0.1, -2],
"k": 15,
"num_candidates": 100
},
"fields": [ "title" ],
"rescore": {
"window_size": 10,
"query": {
"rescore_query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"source": "cosineSimilarity(params.query_vector, 'image-vector') + 1.0",
"params": {
"query_vector": [0.1, -2]
}
}
}
}
}
}
}
Filtered kNN search
kNN 検索 API は、フィルターを使用して検索を制限することをサポートしています。検索は、フィルタークエリにも一致するトップ k
ドキュメントを返します。
次のリクエストは、file-type
フィールドでフィルタリングされた近似 kNN 検索を実行します:
Python
resp = client.search(
index="image-index",
knn={
"field": "image-vector",
"query_vector": [
54,
10,
-2
],
"k": 5,
"num_candidates": 50,
"filter": {
"term": {
"file-type": "png"
}
}
},
fields=[
"title"
],
source=False,
)
print(resp)
Ruby
response = client.search(
index: 'image-index',
body: {
knn: {
field: 'image-vector',
query_vector: [
54,
10,
-2
],
k: 5,
num_candidates: 50,
filter: {
term: {
"file-type": 'png'
}
}
},
fields: [
'title'
],
_source: false
}
)
puts response
Js
const response = await client.search({
index: "image-index",
knn: {
field: "image-vector",
query_vector: [54, 10, -2],
k: 5,
num_candidates: 50,
filter: {
term: {
"file-type": "png",
},
},
},
fields: ["title"],
_source: false,
});
console.log(response);
Console
POST image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [54, 10, -2],
"k": 5,
"num_candidates": 50,
"filter": {
"term": {
"file-type": "png"
}
}
},
"fields": ["title"],
"_source": false
}
フィルターは、k
に一致するドキュメントが返されることを保証するために、近似 kNN 検索 中に 適用されます。これは、近似 kNN 検索が完了した 後に フィルターが適用されるポストフィルタリングアプローチとは対照的です。ポストフィルタリングには、十分な一致するドキュメントがある場合でも、k 結果未満を返すことがあるという欠点があります。
Approximate kNN search and filtering
従来のクエリフィルタリングとは異なり、より制限的なフィルターが通常はクエリを高速化するのに対し、HNSW インデックスを使用した近似 kNN 検索でフィルターを適用すると、パフォーマンスが低下する可能性があります。これは、HNSW グラフを検索するために、フィルター基準を満たす num_candidates
を取得するために追加の探索が必要だからです。
重大なパフォーマンスの低下を避けるために、Lucene はセグメントごとに次の戦略を実装しています:
- フィルターされたドキュメント数が num_candidates 以下の場合、検索は HNSW グラフをバイパスし、フィルターされたドキュメントに対してブルートフォース検索を使用します。
- HNSW グラフを探索している間、探索されたノードの数がフィルターを満たすドキュメントの数を超えた場合、検索はグラフの探索を停止し、フィルターされたドキュメントに対してブルートフォース検索に切り替えます。
Combine approximate kNN with other features
ハイブリッド検索 を実行するには、knn
オプション と query
の両方を提供できます:
Python
resp = client.search(
index="image-index",
query={
"match": {
"title": {
"query": "mountain lake",
"boost": 0.9
}
}
},
knn={
"field": "image-vector",
"query_vector": [
54,
10,
-2
],
"k": 5,
"num_candidates": 50,
"boost": 0.1
},
size=10,
)
print(resp)
Ruby
response = client.search(
index: 'image-index',
body: {
query: {
match: {
title: {
query: 'mountain lake',
boost: 0.9
}
}
},
knn: {
field: 'image-vector',
query_vector: [
54,
10,
-2
],
k: 5,
num_candidates: 50,
boost: 0.1
},
size: 10
}
)
puts response
Js
const response = await client.search({
index: "image-index",
query: {
match: {
title: {
query: "mountain lake",
boost: 0.9,
},
},
},
knn: {
field: "image-vector",
query_vector: [54, 10, -2],
k: 5,
num_candidates: 50,
boost: 0.1,
},
size: 10,
});
console.log(response);
Console
POST image-index/_search
{
"query": {
"match": {
"title": {
"query": "mountain lake",
"boost": 0.9
}
}
},
"knn": {
"field": "image-vector",
"query_vector": [54, 10, -2],
"k": 5,
"num_candidates": 50,
"boost": 0.1
},
"size": 10
}
この検索は、グローバルなトップ k = 5
ベクトルマッチを見つけ、それを match
クエリからのマッチと組み合わせ、最終的にトップ 10 のスコア結果を返します。knn
と query
のマッチは、論理和のように組み合わされます。すなわち、両者の間にブールの または を取るかのように。トップ k
ベクトル結果は、すべてのインデックスシャードにわたるグローバルな最近傍を表します。
各ヒットのスコアは、knn
と query
スコアの合計です。合計の各スコアに重みを与える boost
値を指定できます。上記の例では、スコアは次のように計算されます:
score = 0.9 * match_score + 0.1 * knn_score
knn
オプションは、aggregations
とともに使用することもできます。一般に、Elasticsearch は検索に一致するすべてのドキュメントに対して集計を計算します。したがって、近似 kNN 検索の場合、集計はトップ k
最近のドキュメントに対して計算されます。検索に query
が含まれている場合、集計は knn
と query
のマッチの組み合わせセットに対して計算されます。
Perform semantic search
kNN 検索を使用すると、以前にデプロイされた テキスト埋め込みモデル を使用してセマンティック検索を実行できます。検索用語のリテラルマッチの代わりに、セマンティック検索は検索クエリの意図と文脈的意味に基づいて結果を取得します。
内部では、テキスト埋め込み NLP モデルは、提供された入力クエリ文字列から model_text
と呼ばれる密なベクトルを生成します。次に、それは同じテキスト埋め込み機械学習モデルで作成された密なベクトルを含むインデックスに対して検索されます。検索結果は、モデルによって学習されたセマンティックに類似しています。
セマンティック検索を実行するには:
- 検索対象の入力データの密なベクトル表現を含むインデックスが必要です。
- 入力データから密なベクトルを作成するために使用したのと同じテキスト埋め込みモデルを検索に使用する必要があります。
- テキスト埋め込み NLP モデルのデプロイメントを開始する必要があります。
デプロイされたテキスト埋め込みモデルまたは query_vector_builder
オブジェクト内のモデルデプロイメントを参照し、検索クエリを model_text
として提供します:
Js
(...)
{
"knn": {
"field": "dense-vector-field",
"k": 10,
"num_candidates": 100,
"query_vector_builder": {
"text_embedding": {
"model_id": "my-text-embedding-model",
"model_text": "The opposite of blue"
}
}
}
}
(...)
実行する自然言語処理タスク。text_embedding である必要があります。 |
|
クエリ文字列から密なベクトルを生成するために使用するテキスト埋め込みモデルの ID。検索対象のインデックス内の入力テキストから埋め込みを生成したのと同じモデルを使用します。deployment_id の値を model_id 引数に代わりに使用できます。 |
|
モデルが密なベクトル表現を生成するクエリ文字列。 |
トレーニングされたモデルをデプロイし、それを使用してテキスト埋め込みを作成する方法についての詳細は、この エンドツーエンドの例 を参照してください。
Search multiple kNN fields
ハイブリッド検索に加えて、同時に複数の kNN ベクトルフィールドを検索できます:
Python
resp = client.search(
index="image-index",
query={
"match": {
"title": {
"query": "mountain lake",
"boost": 0.9
}
}
},
knn=[
{
"field": "image-vector",
"query_vector": [
54,
10,
-2
],
"k": 5,
"num_candidates": 50,
"boost": 0.1
},
{
"field": "title-vector",
"query_vector": [
1,
20,
-52,
23,
10
],
"k": 10,
"num_candidates": 10,
"boost": 0.5
}
],
size=10,
)
print(resp)
Ruby
response = client.search(
index: 'image-index',
body: {
query: {
match: {
title: {
query: 'mountain lake',
boost: 0.9
}
}
},
knn: [
{
field: 'image-vector',
query_vector: [
54,
10,
-2
],
k: 5,
num_candidates: 50,
boost: 0.1
},
{
field: 'title-vector',
query_vector: [
1,
20,
-52,
23,
10
],
k: 10,
num_candidates: 10,
boost: 0.5
}
],
size: 10
}
)
puts response
Js
const response = await client.search({
index: "image-index",
query: {
match: {
title: {
query: "mountain lake",
boost: 0.9,
},
},
},
knn: [
{
field: "image-vector",
query_vector: [54, 10, -2],
k: 5,
num_candidates: 50,
boost: 0.1,
},
{
field: "title-vector",
query_vector: [1, 20, -52, 23, 10],
k: 10,
num_candidates: 10,
boost: 0.5,
},
],
size: 10,
});
console.log(response);
k近傍法 (kNN) 検索
k-nearest neighbor (kNN) 検索は、クエリベクトルに最も近い k 個のベクトルを、類似度メトリックによって測定します。
kNNの一般的な使用例には、次のものが含まれます:
- 自然言語処理 (NLP) アルゴリズムに基づく関連性ランキング
- 製品推奨および推薦エンジン
- 画像や動画の類似検索
Search kNN with expected similarity
kNN は強力なツールですが、常に k
最近傍を返そうとします。したがって、knn
を filter
とともに使用すると、関連するすべてのドキュメントをフィルタリングし、検索するのは無関係なものだけになる可能性があります。その場合、knn
は、ベクトル空間で遠く離れた k
最近傍を返すために最善を尽くします。
この懸念を軽減するために、similarity
パラメータが knn
句で利用可能です。この値は、ベクトルがマッチと見なされるために必要な最小類似度です。このパラメータを使用した knn
検索フローは次のようになります:
- ユーザー提供の
filter
クエリを適用します - ベクトル空間を探索して
k
ベクトルを取得します - 設定された
similarity
よりも遠くにあるベクトルは返しません
similarity
は、_score
に変換され、ブーストが適用される前の真の 類似度 です。
設定された各 類似度 に対して、対応する逆 _score
関数があります。これは、_score
の観点からフィルタリングしたい場合に、無関係な結果を正しく拒否するためにこの小さな変換を行うことができるようにするためです。
l2_norm
:sqrt((1 / _score) - 1)
cosine
:(2 * _score) - 1
dot_product
:(2 * _score) - 1
- `````_score < 1`````: `````1 - (1 / _score)
_score >= 1
:_score - 1
ここに例があります。この例では、query_vector
に対して k
最近傍を検索します。ただし、filter
が適用され、見つかったベクトルが少なくとも提供された similarity
を持つことが要求されます。
Python
resp = client.search(
index="image-index",
knn={
"field": "image-vector",
"query_vector": [
1,
5,
-20
],
"k": 5,
"num_candidates": 50,
"similarity": 36,
"filter": {
"term": {
"file-type": "png"
}
}
},
fields=[
"title"
],
source=False,
)
print(resp)
Ruby
response = client.search(
index: 'image-index',
body: {
knn: {
field: 'image-vector',
query_vector: [
1,
5,
-20
],
k: 5,
num_candidates: 50,
similarity: 36,
filter: {
term: {
"file-type": 'png'
}
}
},
fields: [
'title'
],
_source: false
}
)
puts response
Js
const response = await client.search({
index: "image-index",
knn: {
field: "image-vector",
query_vector: [1, 5, -20],
k: 5,
num_candidates: 50,
similarity: 36,
filter: {
term: {
"file-type": "png",
},
},
},
fields: ["title"],
_source: false,
});
console.log(response);
Console
POST image-index/_search
{
"knn": {
"field": "image-vector",
"query_vector": [1, 5, -20],
"k": 5,
"num_candidates": 50,
"similarity": 36,
"filter": {
"term": {
"file-type": "png"
}
}
},
"fields": ["title"],
"_source": false
}
データセット内で、png
のファイルタイプを持つ唯一のドキュメントは [42, 8, -15]
のベクトルを持っています。l2_norm
の [42, 8, -15]
と [1, 5, -20]
の間の距離は 41.412
であり、これは設定された類似度 36
よりも大きいです。つまり、この検索はヒットを返しません。
Nested kNN Search
テキストが特定のモデルのトークン制限を超えることは一般的であり、個々のチャンクの埋め込みを構築する前にチャンク化が必要です。nested
を dense_vector
とともに使用すると、トップレベルのドキュメントメタデータをコピーせずに最近のパッセージの取得を実現できます。
ここに、ベクトルとフィルタリング用のいくつかのトップレベルメタデータを保存するシンプルなパッセージベクトルインデックスがあります。
Python
resp = client.indices.create(
index="passage_vectors",
mappings={
"properties": {
"full_text": {
"type": "text"
},
"creation_time": {
"type": "date"
},
"paragraph": {
"type": "nested",
"properties": {
"vector": {
"type": "dense_vector",
"dims": 2,
"index_options": {
"type": "hnsw"
}
},
"text": {
"type": "text",
"index": False
}
}
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'passage_vectors',
body: {
mappings: {
properties: {
full_text: {
type: 'text'
},
creation_time: {
type: 'date'
},
paragraph: {
type: 'nested',
properties: {
vector: {
type: 'dense_vector',
dims: 2,
index_options: {
type: 'hnsw'
}
},
text: {
type: 'text',
index: false
}
}
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "passage_vectors",
mappings: {
properties: {
full_text: {
type: "text",
},
creation_time: {
type: "date",
},
paragraph: {
type: "nested",
properties: {
vector: {
type: "dense_vector",
dims: 2,
index_options: {
type: "hnsw",
},
},
text: {
type: "text",
index: false,
},
},
},
},
},
});
console.log(response);
Console
PUT passage_vectors
{
"mappings": {
"properties": {
"full_text": {
"type": "text"
},
"creation_time": {
"type": "date"
},
"paragraph": {
"type": "nested",
"properties": {
"vector": {
"type": "dense_vector",
"dims": 2,
"index_options": {
"type": "hnsw"
}
},
"text": {
"type": "text",
"index": false
}
}
}
}
}
}
上記のマッピングを使用すると、個々のパッセージテキストを保存しながら、複数のパッセージベクトルをインデックスできます。
Python
resp = client.bulk(
index="passage_vectors",
refresh=True,
operations=[
{
"index": {
"_id": "1"
}
},
{
"full_text": "first paragraph another paragraph",
"creation_time": "2019-05-04",
"paragraph": [
{
"vector": [
0.45,
45
],
"text": "first paragraph",
"paragraph_id": "1"
},
{
"vector": [
0.8,
0.6
],
"text": "another paragraph",
"paragraph_id": "2"
}
]
},
{
"index": {
"_id": "2"
}
},
{
"full_text": "number one paragraph number two paragraph",
"creation_time": "2020-05-04",
"paragraph": [
{
"vector": [
1.2,
4.5
],
"text": "number one paragraph",
"paragraph_id": "1"
},
{
"vector": [
-1,
42
],
"text": "number two paragraph",
"paragraph_id": "2"
}
]
}
],
)
print(resp)
Ruby
response = client.bulk(
index: 'passage_vectors',
refresh: true,
body: [
{
index: {
_id: '1'
}
},
{
full_text: 'first paragraph another paragraph',
creation_time: '2019-05-04',
paragraph: [
{
vector: [
0.45,
45
],
text: 'first paragraph',
paragraph_id: '1'
},
{
vector: [
0.8,
0.6
],
text: 'another paragraph',
paragraph_id: '2'
}
]
},
{
index: {
_id: '2'
}
},
{
full_text: 'number one paragraph number two paragraph',
creation_time: '2020-05-04',
paragraph: [
{
vector: [
1.2,
4.5
],
text: 'number one paragraph',
paragraph_id: '1'
},
{
vector: [
-1,
42
],
text: 'number two paragraph',
paragraph_id: '2'
}
]
}
]
)
puts response
Js
const response = await client.bulk({
index: "passage_vectors",
refresh: "true",
operations: [
{
index: {
_id: "1",
},
},
{
full_text: "first paragraph another paragraph",
creation_time: "2019-05-04",
paragraph: [
{
vector: [0.45, 45],
text: "first paragraph",
paragraph_id: "1",
},
{
vector: [0.8, 0.6],
text: "another paragraph",
paragraph_id: "2",
},
],
},
{
index: {
_id: "2",
},
},
{
full_text: "number one paragraph number two paragraph",
creation_time: "2020-05-04",
paragraph: [
{
vector: [1.2, 4.5],
text: "number one paragraph",
paragraph_id: "1",
},
{
vector: [-1, 42],
text: "number two paragraph",
paragraph_id: "2",
},
],
},
],
});
console.log(response);
Console
POST passage_vectors/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "full_text": "first paragraph another paragraph", "creation_time": "2019-05-04", "paragraph": [ { "vector": [ 0.45, 45 ], "text": "first paragraph", "paragraph_id": "1" }, { "vector": [ 0.8, 0.6 ], "text": "another paragraph", "paragraph_id": "2" } ] }
{ "index": { "_id": "2" } }
{ "full_text": "number one paragraph number two paragraph", "creation_time": "2020-05-04", "paragraph": [ { "vector": [ 1.2, 4.5 ], "text": "number one paragraph", "paragraph_id": "1" }, { "vector": [ -1, 42 ], "text": "number two paragraph", "paragraph_id": "2" } ] }
クエリは、典型的な kNN 検索に非常に似ているように見えます:
Python
resp = client.search(
index="passage_vectors",
fields=[
"full_text",
"creation_time"
],
source=False,
knn={
"query_vector": [
0.45,
45
],
"field": "paragraph.vector",
"k": 2,
"num_candidates": 2
},
)
print(resp)
Ruby
response = client.search(
index: 'passage_vectors',
body: {
fields: [
'full_text',
'creation_time'
],
_source: false,
knn: {
query_vector: [
0.45,
45
],
field: 'paragraph.vector',
k: 2,
num_candidates: 2
}
}
)
puts response
Js
const response = await client.search({
index: "passage_vectors",
fields: ["full_text", "creation_time"],
_source: false,
knn: {
query_vector: [0.45, 45],
field: "paragraph.vector",
k: 2,
num_candidates: 2,
},
});
console.log(response);
Console
POST passage_vectors/_search
{
"fields": ["full_text", "creation_time"],
"_source": false,
"knn": {
"query_vector": [
0.45,
45
],
"field": "paragraph.vector",
"k": 2,
"num_candidates": 2
}
}
以下に注意してください。合計 4 つのベクトルがあるにもかかわらず、2 つのドキュメントが返されます。ネストされた dense_vectors に対する kNN 検索は、常にトップレベルのドキュメントに対してトップ結果を多様化します。つまり、"k"
トップレベルのドキュメントが返され、最も近いパッセージベクトル (例: "paragraph.vector"
) によってスコア付けされます。
Console-Result
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "passage_vectors",
"_id": "1",
"_score": 1.0,
"fields": {
"creation_time": [
"2019-05-04T00:00:00.000Z"
],
"full_text": [
"first paragraph another paragraph"
]
}
},
{
"_index": "passage_vectors",
"_id": "2",
"_score": 0.9997144,
"fields": {
"creation_time": [
"2020-05-04T00:00:00.000Z"
],
"full_text": [
"number one paragraph number two paragraph"
]
}
}
]
}
}
トップレベルのドキュメントメタデータでフィルタリングしたい場合は、filter
を knn
句に追加することでこれを行うことができます。
filter
は常にトップレベルのドキュメントメタデータに対して行われます。これは、nested
フィールドメタデータに基づいてフィルタリングできないことを意味します。
Python
resp = client.search(
index="passage_vectors",
fields=[
"creation_time",
"full_text"
],
source=False,
knn={
"query_vector": [
0.45,
45
],
"field": "paragraph.vector",
"k": 2,
"num_candidates": 2,
"filter": {
"bool": {
"filter": [
{
"range": {
"creation_time": {
"gte": "2019-05-01",
"lte": "2019-05-05"
}
}
}
]
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'passage_vectors',
body: {
fields: [
'creation_time',
'full_text'
],
_source: false,
knn: {
query_vector: [
0.45,
45
],
field: 'paragraph.vector',
k: 2,
num_candidates: 2,
filter: {
bool: {
filter: [
{
range: {
creation_time: {
gte: '2019-05-01',
lte: '2019-05-05'
}
}
}
]
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "passage_vectors",
fields: ["creation_time", "full_text"],
_source: false,
knn: {
query_vector: [0.45, 45],
field: "paragraph.vector",
k: 2,
num_candidates: 2,
filter: {
bool: {
filter: [
{
range: {
creation_time: {
gte: "2019-05-01",
lte: "2019-05-05",
},
},
},
],
},
},
},
});
console.log(response);
Console
POST passage_vectors/_search
{
"fields": [
"creation_time",
"full_text"
],
"_source": false,
"knn": {
"query_vector": [
0.45,
45
],
"field": "paragraph.vector",
"k": 2,
"num_candidates": 2,
"filter": {
"bool": {
"filter": [
{
"range": {
"creation_time": {
"gte": "2019-05-01",
"lte": "2019-05-05"
}
}
}
]
}
}
}
}
現在、トップレベルの "creation_time"
に基づいてフィルタリングされており、その範囲内に該当するドキュメントは 1 つだけです。
Console-Result
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "passage_vectors",
"_id": "1",
"_score": 1.0,
"fields": {
"creation_time": [
"2019-05-04T00:00:00.000Z"
],
"full_text": [
"first paragraph another paragraph"
]
}
}
]
}
}
Nested kNN Search with Inner hits
さらに、一致したドキュメントの最近のパッセージを抽出したい場合は、knn
句に inner_hits を提供できます。
inner_hits
と複数の knn
句を使用する場合は、inner_hits.name
フィールドを指定してください。そうしないと、名前の衝突が発生し、検索リクエストが失敗する可能性があります。
Python
resp = client.search(
index="passage_vectors",
fields=[
"creation_time",
"full_text"
],
source=False,
knn={
"query_vector": [
0.45,
45
],
"field": "paragraph.vector",
"k": 2,
"num_candidates": 2,
"inner_hits": {
"_source": False,
"fields": [
"paragraph.text"
],
"size": 1
}
},
)
print(resp)
Js
const response = await client.search({
index: "passage_vectors",
fields: ["creation_time", "full_text"],
_source: false,
knn: {
query_vector: [0.45, 45],
field: "paragraph.vector",
k: 2,
num_candidates: 2,
inner_hits: {
_source: false,
fields: ["paragraph.text"],
size: 1,
},
},
});
console.log(response);
Console
POST passage_vectors/_search
{
"fields": [
"creation_time",
"full_text"
],
"_source": false,
"knn": {
"query_vector": [
0.45,
45
],
"field": "paragraph.vector",
"k": 2,
"num_candidates": 2,
"inner_hits": {
"_source": false,
"fields": [
"paragraph.text"
],
"size": 1
}
}
}
Console-Result
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "passage_vectors",
"_id": "1",
"_score": 1.0,
"fields": {
"creation_time": [
"2019-05-04T00:00:00.000Z"
],
"full_text": [
"first paragraph another paragraph"
]
},
"inner_hits": {
"paragraph": {
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "passage_vectors",
"_id": "1",
"_nested": {
"field": "paragraph",
"offset": 0
},
"_score": 1.0,
"fields": {
"paragraph": [
{
"text": [
"first paragraph"
]
}
]
}
}
]
}
}
}
},
{
"_index": "passage_vectors",
"_id": "2",
"_score": 0.9997144,
"fields": {
"creation_time": [
"2020-05-04T00:00:00.000Z"
],
"full_text": [
"number one paragraph number two paragraph"
]
},
"inner_hits": {
"paragraph": {
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 0.9997144,
"hits": [
{
"_index": "passage_vectors",
"_id": "2",
"_nested": {
"field": "paragraph",
"offset": 1
},
"_score": 0.9997144,
"fields": {
"paragraph": [
{
"text": [
"number two paragraph"
]
}
]
}
}
]
}
}
}
}
]
}
}
Indexing considerations
近似 kNN 検索の場合、Elasticsearch は各セグメントの密なベクトル値を HNSW グラフ として保存します。近似 kNN 検索のためのベクトルのインデックス作成には、これらのグラフを構築するのにかかるコストのため、かなりの時間がかかる場合があります。インデックスおよびバルクリクエストのクライアントリクエストタイムアウトを増やす必要があるかもしれません。近似 kNN 調整ガイド には、インデックス作成パフォーマンスに関する重要なガイダンスと、インデックス構成が検索パフォーマンスにどのように影響するかが含まれています。
検索時の調整パラメータに加えて、HNSW アルゴリズムには、グラフの構築コスト、検索速度、および精度のトレードオフを持つインデックス時のパラメータがあります。dense_vector
マッピングを設定する際に、これらのパラメータを調整するために index_options
引数を使用できます。
Python
resp = client.indices.create(
index="image-index",
mappings={
"properties": {
"image-vector": {
"type": "dense_vector",
"dims": 3,
"similarity": "l2_norm",
"index_options": {
"type": "hnsw",
"m": 32,
"ef_construction": 100
}
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'image-index',
body: {
mappings: {
properties: {
"image-vector": {
type: 'dense_vector',
dims: 3,
similarity: 'l2_norm',
index_options: {
type: 'hnsw',
m: 32,
ef_construction: 100
}
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "image-index",
mappings: {
properties: {
"image-vector": {
type: "dense_vector",
dims: 3,
similarity: "l2_norm",
index_options: {
type: "hnsw",
m: 32,
ef_construction: 100,
},
},
},
},
});
console.log(response);
Console
PUT image-index
{
"mappings": {
"properties": {
"image-vector": {
"type": "dense_vector",
"dims": 3,
"similarity": "l2_norm",
"index_options": {
"type": "hnsw",
"m": 32,
"ef_construction": 100
}
}
}
}
}
Limitations for approximate kNN search
- クロスクラスタ検索 で kNN 検索を使用する場合、
ccs_minimize_roundtrips
オプションはサポートされていません。 - Elasticsearch は、効率的な kNN 検索をサポートするために HNSW アルゴリズム を使用します。ほとんどの kNN アルゴリズムと同様に、HNSW は結果の精度を犠牲にして検索速度を向上させる近似手法です。これは、返される結果が常に真の k 最近傍ではないことを意味します。
近似 kNN 検索は常に、シャード全体でグローバルなトップ k
マッチを収集するために dfs_query_then_fetch
検索タイプを使用します。kNN 検索を実行する際に search_type
を明示的に設定することはできません。
Exact kNN
正確な kNN 検索を実行するには、ベクトル関数を使用した script_score
クエリを使用します。
- 1. 1 つ以上の
dense_vector
フィールドを明示的にマッピングします。近似 kNN を使用するつもりがない場合は、index
マッピングオプションをfalse
に設定します。これにより、インデックス作成速度が大幅に向上する可能性があります。
Python
resp = client.indices.create(
index="product-index",
mappings={
"properties": {
"product-vector": {
"type": "dense_vector",
"dims": 5,
"index": False
},
"price": {
"type": "long"
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'product-index',
body: {
mappings: {
properties: {
"product-vector": {
type: 'dense_vector',
dims: 5,
index: false
},
price: {
type: 'long'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "product-index",
mappings: {
properties: {
"product-vector": {
type: "dense_vector",
dims: 5,
index: false,
},
price: {
type: "long",
},
},
},
});
console.log(response);
Console
PUT product-index
{
"mappings": {
"properties": {
"product-vector": {
"type": "dense_vector",
"dims": 5,
"index": false
},
"price": {
"type": "long"
}
}
}
}
Console
POST product-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "product-vector": [230.0, 300.33, -34.8988, 15.555, -200.0], "price": 1599 }
{ "index": { "_id": "2" } }
{ "product-vector": [-0.5, 100.0, -13.0, 14.8, -156.0], "price": 799 }
{ "index": { "_id": "3" } }
{ "product-vector": [0.5, 111.3, -13.0, 14.8, -156.0], "price": 1099 }
...
- 3. 検索 API を使用して、ベクトル関数を含む
script_score
クエリを実行します。
一致するドキュメントの数をベクトル関数に渡すのを制限するには、script_score.query
パラメータにフィルタークエリを指定することをお勧めします。必要に応じて、このパラメータにmatch_all
クエリ を使用してすべてのドキュメントに一致させることができます。ただし、すべてのドキュメントに一致させると、検索の遅延が大幅に増加する可能性があります。
Python
resp = client.search(
index="product-index",
query={
"script_score": {
"query": {
"bool": {
"filter": {
"range": {
"price": {
"gte": 1000
}
}
}
}
},
"script": {
"source": "cosineSimilarity(params.queryVector, 'product-vector') + 1.0",
"params": {
"queryVector": [
-0.5,
90,
-10,
14.8,
-156
]
}
}
}
},
)
print(resp)
Ruby
response = client.search(
index: 'product-index',
body: {
query: {
script_score: {
query: {
bool: {
filter: {
range: {
price: {
gte: 1000
}
}
}
}
},
script: {
source: "cosineSimilarity(params.queryVector, 'product-vector') + 1.0",
params: {
"queryVector": [
-0.5,
90,
-10,
14.8,
-156
]
}
}
}
}
}
)
puts response
Js
const response = await client.search({
index: "product-index",
query: {
script_score: {
query: {
bool: {
filter: {
range: {
price: {
gte: 1000,
},
},
},
},
},
script: {
source: "cosineSimilarity(params.queryVector, 'product-vector') + 1.0",
params: {
queryVector: [-0.5, 90, -10, 14.8, -156],
},
},
},
},
});
console.log(response);
Console
POST product-index/_search
{
"query": {
"script_score": {
"query" : {
"bool" : {
"filter" : {
"range" : {
"price" : {
"gte": 1000
}
}
}
}
},
"script": {
"source": "cosineSimilarity(params.queryVector, 'product-vector') + 1.0",
"params": {
"queryVector": [-0.5, 90.0, -10, 14.8, -156.0]
}
}
}
}
}