チュートリアル: ELSERを用いたセマンティックサーチ
Elastic Learned Sparse EncodeR - またはELSER - は、ElasticによってトレーニングされたNLPモデルで、スパースベクトル表現を使用してセマンティックサーチを実行することを可能にします。検索用語の文字通りの一致ではなく、セマンティックサーチは検索クエリの意図と文脈的意味に基づいて結果を取得します。
このチュートリアルの指示では、ELSERを使用してデータに対してセマンティックサーチを実行する方法を示します。
Elastic Stackでセマンティックサーチを最も簡単に実行する方法については、semantic_text
のエンドツーエンドチュートリアルを参照してください。
ELSERを使用したセマンティックサーチでは、各フィールドから抽出された最初の512トークンのみが考慮されます。詳細については、このページを参照してください。
要件
ELSERを使用してセマンティックサーチを実行するには、クラスターにNLPモデルがデプロイされている必要があります。モデルのダウンロードとデプロイ方法については、ELSERドキュメントを参照してください。
ELSERモデルをデプロイして使用するための最小限の専用MLノードサイズは、Elasticsearch Serviceで4GBです。デプロイメントのオートスケーリングがオフの場合、オートスケーリングをオンにすることをお勧めします。これにより、需要に基づいてリソースを動的に調整できます。より多くの割り当てや、割り当てごとのスレッド数を増やすことで、より良いパフォーマンスが得られますが、これにはより大きなMLノードが必要です。オートスケーリングは、必要に応じてより大きなノードを提供します。オートスケーリングがオフの場合は、適切なサイズのノードを自分で提供する必要があります。
インデックスマッピングの作成
まず、モデルがあなたのテキストに基づいて作成したトークンを含む宛先インデックスのマッピングを作成する必要があります。宛先インデックスには、ELSER出力をインデックスするためにsparse_vector
またはrank_features
フィールドタイプを持つフィールドが必要です。
ELSER出力は、sparse_vector
またはrank_features
フィールドタイプのフィールドに取り込まれる必要があります。そうしないと、Elasticsearchはトークン-重みペアをドキュメント内の大量のフィールドとして解釈します。このようなエラーが発生した場合: "Limit of total fields [1000] has been exceeded while adding new fields"
、ELSER出力フィールドが正しくマッピングされておらず、フィールドタイプがsparse_vector
またはrank_features
とは異なります。
Python
resp = client.indices.create(
index="my-index",
mappings={
"properties": {
"content_embedding": {
"type": "sparse_vector"
},
"content": {
"type": "text"
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'my-index',
body: {
mappings: {
properties: {
content_embedding: {
type: 'sparse_vector'
},
content: {
type: 'text'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index",
mappings: {
properties: {
content_embedding: {
type: "sparse_vector",
},
content: {
type: "text",
},
},
},
});
console.log(response);
コンソール
PUT my-index
{
"mappings": {
"properties": {
"content_embedding": {
"type": "sparse_vector"
},
"content": {
"type": "text"
}
}
}
}
生成されたトークンを含むフィールドの名前です。 それは次のステップの推論パイプライン構成で参照される必要があります。 |
|
トークンを含むフィールドはsparse_vector フィールドです。 |
|
スパースベクトル表現を作成するためのフィールドの名前です。 この例では、フィールドの名前は content です。それは次のステップの推論パイプライン構成で参照される必要があります。 |
|
この例ではテキストのフィールドタイプです。 |
スペースを最適化する方法については、ELSERトークンをドキュメントソースから除外してディスクスペースを節約するセクションを参照してください。
推論プロセッサを使用したインジェストパイプラインの作成
ELSERを使用して、パイプラインで取り込まれるデータに対して推論を行うインジェストパイプラインを作成します。
Python
resp = client.ingest.put_pipeline(
id="elser-v2-test",
processors=[
{
"inference": {
"model_id": ".elser_model_2",
"input_output": [
{
"input_field": "content",
"output_field": "content_embedding"
}
]
}
}
],
)
print(resp)
Ruby
response = client.ingest.put_pipeline(
id: 'elser-v2-test',
body: {
processors: [
{
inference: {
model_id: '.elser_model_2',
input_output: [
{
input_field: 'content',
output_field: 'content_embedding'
}
]
}
}
]
}
)
puts response
Js
const response = await client.ingest.putPipeline({
id: "elser-v2-test",
processors: [
{
inference: {
model_id: ".elser_model_2",
input_output: [
{
input_field: "content",
output_field: "content_embedding",
},
],
},
},
],
});
console.log(response);
コンソール
PUT _ingest/pipeline/elser-v2-test
{
"processors": [
{
"inference": {
"model_id": ".elser_model_2",
"input_output": [
{
"input_field": "content",
"output_field": "content_embedding"
}
]
}
}
]
}
推論プロセスのためのinput_field を定義する構成オブジェクトと、推論結果を含むoutput_field 。 |
データのロード
このステップでは、後で推論インジェストパイプラインでトークンを抽出するために使用するデータをロードします。
MS MARCO Passage Rankingデータセットのサブセットであるmsmarco-passagetest2019-top1000
データセットを使用します。これは、200のクエリと、それぞれに関連するテキストパッセージのリストが付属しています。すべてのユニークなパッセージとそのIDは、そのデータセットから抽出され、tsvファイルにまとめられています。
ファイルをダウンロードし、UIの[ファイルアップローダー](https://www.elastic.co/guide/en/kibana/8.15/connect-to-elasticsearch.html#upload-data-kibana)を使用してクラスターにアップロードします。データが分析された後、**設定を上書き**をクリックします。**フィールド名の編集**の下で、最初の列に`````id`````、2番目の列に`````content`````を割り当てます。**適用**をクリックし、次に**インポート**をクリックします。インデックスに`````test-data`````という名前を付け、**インポート**をクリックします。アップロードが完了すると、182,469ドキュメントを持つ`````test-data`````という名前のインデックスが表示されます。
## 推論インジェストパイプラインを通じてデータを取り込む
ELSERを推論モデルとして使用する推論パイプラインを通じてデータを再インデックス化することで、テキストからトークンを作成します。
#### Python
``````python
resp = client.reindex(
wait_for_completion=False,
source={
"index": "test-data",
"size": 50
},
dest={
"index": "my-index",
"pipeline": "elser-v2-test"
},
)
print(resp)
`
Ruby
response = client.reindex(
wait_for_completion: false,
body: {
source: {
index: 'test-data',
size: 50
},
dest: {
index: 'my-index',
pipeline: 'elser-v2-test'
}
}
)
puts response
Js
const response = await client.reindex({
wait_for_completion: "false",
source: {
index: "test-data",
size: 50,
},
dest: {
index: "my-index",
pipeline: "elser-v2-test",
},
});
console.log(response);
コンソール
POST _reindex?wait_for_completion=false
{
"source": {
"index": "test-data",
"size": 50
},
"dest": {
"index": "my-index",
"pipeline": "elser-v2-test"
}
}
再インデックス化のデフォルトバッチサイズは1000です。size を小さい数に減らすことで、再インデックス化プロセスの更新が迅速になり、進捗を密接に追跡し、早期にエラーを検出できます。 |
呼び出しは進捗を監視するためのタスクIDを返します:
Python
resp = client.tasks.get(
task_id="<task_id>",
)
print(resp)
Js
const response = await client.tasks.get({
task_id: "<task_id>",
});
console.log(response);
コンソール
GET _tasks/<task_id>
トレーニング済みモデルUIを開き、ELSERの下のパイプラインタブを選択して進捗を追跡することもできます。
大規模なデータセットの再インデックス化には時間がかかる場合があります。このワークフローをデータセットのサブセットのみを使用してテストできます。再インデックス化プロセスをキャンセルし、再インデックス化されたサブセットの埋め込みのみを生成します。次のAPIリクエストは再インデックス化タスクをキャンセルします:
Python
resp = client.tasks.cancel(
task_id="<task_id>",
)
print(resp)
Js
const response = await client.tasks.cancel({
task_id: "<task_id>",
});
console.log(response);
コンソール
POST _tasks/<task_id>/_cancel
スパースベクトルクエリを使用したセマンティックサーチ
セマンティックサーチを実行するには、sparse_vector
クエリを使用し、クエリテキストとELSERモデルに関連付けられた推論IDを提供します。以下の例では、クエリテキスト「ランニング後の筋肉痛を避ける方法は?」を使用し、content_embedding
フィールドには生成されたELSER出力が含まれています:
Python
resp = client.search(
index="my-index",
query={
"sparse_vector": {
"field": "content_embedding",
"inference_id": "my-elser-endpoint",
"query": "How to avoid muscle soreness after running?"
}
},
)
print(resp)
Js
const response = await client.search({
index: "my-index",
query: {
sparse_vector: {
field: "content_embedding",
inference_id: "my-elser-endpoint",
query: "How to avoid muscle soreness after running?",
},
},
});
console.log(response);
コンソール
GET my-index/_search
{
"query":{
"sparse_vector":{
"field": "content_embedding",
"inference_id": "my-elser-endpoint",
"query": "How to avoid muscle soreness after running?"
}
}
}
結果は、my-index
インデックスからのクエリテキストに最も意味的に近い上位10ドキュメントです。結果には、関連する検索結果ごとの抽出されたトークンとその重みも含まれています。トークンは関連性を捉える学習された関連付けであり、同義語ではありません。トークンについて詳しく知りたい場合は、このページを参照してください。ソースからトークンを除外することも可能で、このセクションを参照して詳細を学んでください。
コンソール-結果
"hits": {
"total": {
"value": 10000,
"relation": "gte"
},
"max_score": 26.199875,
"hits": [
{
"_index": "my-index",
"_id": "FPr9HYsBag9jXmT8lEpI",
"_score": 26.199875,
"_source": {
"content_embedding": {
"muscular": 0.2821541,
"bleeding": 0.37929374,
"foods": 1.1718726,
"delayed": 1.2112266,
"cure": 0.6848574,
"during": 0.5886185,
"fighting": 0.35022718,
"rid": 0.2752442,
"soon": 0.2967024,
"leg": 0.37649947,
"preparation": 0.32974035,
"advance": 0.09652356,
(...)
},
"id": 1713868,
"model_id": ".elser_model_2",
"content": "For example, if you go for a run, you will mostly use the muscles in your lower body. Give yourself 2 days to rest those muscles so they have a chance to heal before you exercise them again. Not giving your muscles enough time to rest can cause muscle damage, rather than muscle development."
}
},
(...)
]
}
他のクエリとのセマンティックサーチの組み合わせ
sparse_vector
を他のクエリと複合クエリで組み合わせることができます。たとえば、[sparse_vector
]クエリと同じ(または異なる)クエリテキストを持つブールまたは全文検索クエリでフィルター句を使用します。これにより、両方のクエリからの検索結果を組み合わせることができます。
#### Python
``````python
resp = client.search(
index="my-index",
query={
"bool": {
"should": [
{
"sparse_vector": {
"field": "content_embedding",
"inference_id": "my-elser-endpoint",
"query": "How to avoid muscle soreness after running?",
"boost": 1
}
},
{
"query_string": {
"query": "toxins",
"boost": 4
}
}
]
}
},
min_score=10,
)
print(resp)
`
Js
const response = await client.search({
index: "my-index",
query: {
bool: {
should: [
{
sparse_vector: {
field: "content_embedding",
inference_id: "my-elser-endpoint",
query: "How to avoid muscle soreness after running?",
boost: 1,
},
},
{
query_string: {
query: "toxins",
boost: 4,
},
},
],
},
},
min_score: 10,
});
console.log(response);
コンソール
GET my-index/_search
{
"query": {
"bool": {
"should": [
{
"sparse_vector": {
"field": "content_embedding",
"inference_id": "my-elser-endpoint",
"query": "How to avoid muscle soreness after running?",
"boost": 1
}
},
{
"query_string": {
"query": "toxins",
"boost": 4
}
}
]
}
},
"min_score": 10
}
sparse_vector とquery_string の両方のクエリがshould 句のbool クエリにあります。 |
|
boost の値は1 で、sparse_vector クエリのデフォルト値です。これは、このクエリの結果の関連性スコアがブーストされないことを意味します。 |
|
boost の値は4 で、query_string クエリの値です。このクエリの結果の関連性スコアが増加し、検索結果でのランクが上がります。 |
|
10 以上のスコアを持つ結果のみが表示されます。 |
パフォーマンスの最適化
ELSERトークンをドキュメントソースから除外してディスクスペースを節約する
ELSERによって生成されたトークンは、スパースベクトルクエリで使用するためにインデックス化される必要があります。ただし、ドキュメントソースにそれらの用語を保持する必要はありません。ソース除外マッピングを使用して、ドキュメントソースからELSER用語を削除することでディスクスペースを節約できます。
再インデックスは、ドキュメントソースを使用して宛先インデックスをポピュレートします。ELSER用語がソースから除外された後は、再インデックス化を通じて回復できません。ソースからトークンを除外することは、将来的に再インデックス化が必要ないと確信している場合にのみ適用すべきスペース節約の最適化です!このトレードオフを慎重に考慮し、ELSER用語をソースから除外することが特定の要件やユースケースに合致していることを確認することが重要です。_source
フィールドの無効化や、_source
からのフィールドの含める/除外するセクションを注意深く確認して、_source
からトークンを除外することの可能な結果について詳しく学んでください。
#### Python
``````python
resp = client.indices.create(
index="my-index",
mappings={
"_source": {
"excludes": [
"content_embedding"
]
},
"properties": {
"content_embedding": {
"type": "sparse_vector"
},
"content": {
"type": "text"
}
}
},
)
print(resp)
`
Ruby
response = client.indices.create(
index: 'my-index',
body: {
mappings: {
_source: {
excludes: [
'content_embedding'
]
},
properties: {
content_embedding: {
type: 'sparse_vector'
},
content: {
type: 'text'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index",
mappings: {
_source: {
excludes: ["content_embedding"],
},
properties: {
content_embedding: {
type: "sparse_vector",
},
content: {
type: "text",
},
},
},
});
console.log(response);
コンソール
PUT my-index
{
"mappings": {
"_source": {
"excludes": [
"content_embedding"
]
},
"properties": {
"content_embedding": {
"type": "sparse_vector"
},
"content": {
"type": "text"
}
}
}
}
データに応じて、sparse_vector
クエリはtrack_total_hits: false
でより速くなる場合があります。
さらなる読み物
- ELSERのダウンロードとデプロイ方法
- ELSERの制限
- Elastic Stackにおける情報検索の改善: Elastic Learned Sparse Encoder、私たちの新しい検索モデルの紹介
インタラクティブな例
elasticsearch-labs
リポジトリには、Elasticsearch Pythonクライアントを使用したELSER駆動のセマンティックサーチのインタラクティブな例があります。