今日は前回の記事(OpenSearch の様々な検索 (1) : ベクトル検索)に続いて、セマンティック検索 / ニューラル検索を触っていきます。
セマンティック検索 / ニューラル検索
セマンティック検索とニューラル検索は非常に似た概念です。
例えば上記のAWS OpenSearchのドキュメントでニューラル検索のページから、チュートリアルのリンクに飛ぶとOpenSearchの以下のページに遷移します。
このーページではセマンティック検索、となっておりこの2つはほぼ同じものとしてドキュメントでは扱われていますが、実際には以下の違いがあります。
ニューラル検索とセマンティック検索はいずれも「意味的な関連性」に基づく検索手法です。セマンティック検索は、言葉の意味や文脈を理解して関連情報を探す広い概念で、その実装方式の一つとしてニューラル検索が存在します。ニューラル検索とは、ニューラルネットで生成したベクトル埋め込みを用いて数値的に近さを計算します。
つまり、セマンティック検索の中にニューラル検索が含まれるという関係にあります。
ニューラル検索とベクトル検索の違い
通常のベクトル検索は、利用者が外部の埋め込みモデルでベクトルを生成し、それをOpenSearchに登録して検索します。
一方、ニューラル検索はOpenSearch内部でモデルを呼び出して自動的に埋め込みを生成し、検索まで一気通貫で処理します。
つまり、ニューラル検索は埋め込み生成の管理を利用者側から抽象化し、より簡単に意味検索を実現できる仕組みです。
このため、この2つはどちらともベクトルを用いたベクトル検索ですが、運用上
ニューラル検索:OpenSearch内部で埋め込みモデルを呼び出す
ベクトル検索:外部埋め込みモデルでベクトル化を行った後OpenSearchと連携する
という運用上の違いがあります。つまりどちらもベクトル検索だが、埋め込み生成のタイミング・場所が異なる、という違いです。
作業の前にOpenSearch専門用語の整理
いまからの手順では複数の専門用語が出てくるためいかに整理を行っておきます。
取り込み(インジェスト)API:システムにデータをロードするためのツールです。インジェストAPIは、インジェストパイプラインやインジェストプロセッサと連携して、さまざまなソースや形式のデータを処理または変換します。
取り込み(インジェスト)パイプライン:ドキュメントがインデックスに投入される際に適用される一連の処理を起動します。処理はプロセッサを呼び出すことで行われ、埋め込み(ベクトル化)、データのフィルタリング、変換などが行われます。
取り込み(インジェスト)プロセッサ:インジェストパイプラインの中核コンポーネントです。インデックス作成前にドキュメントを前処理します。ここに生成AIモデルを指定することで埋め込み(ベクトル化)が自動で行われます。
さっそくやってみる
前回の記事ではOpenSearch Serverlessを使いましたが、今日はクラスターを使います。
クラスターの起動手順は標準でよいため、このブログからは割愛しますが、必ず以下のオプションをオンにしておいてください。

1. MLタスク拡張設定
まずはOpenSearchのクラスタでMLタスクを実行できるよう設定し、ML関連タスクがメモリを最大限まで利用できるように以下のコマンドを実行します。
PUT _cluster/settings
{
"persistent": {
"plugins.ml_commons.only_run_on_ml_node": "false",
"plugins.ml_commons.native_memory_threshold": "99"
}
}
以下のレスポンスが戻れば成功です。
{
"acknowledged": true,
"persistent": {
"plugins": {
"ml_commons": {
"only_run_on_ml_node": "false",
"native_memory_threshold": "99"
}
}
},
"transient": {}
}
2. モデルの登録
OpenSearch単独ではベクトル化の埋め込み処理は行えません。このため、外部モデルを指定することでインジェストプロセッサが動作するようになります。
POST /_plugins/_ml/models/_register?deploy=true
{
"name": "huggingface/sentence-transformers/msmarco-distilbert-base-tas-b",
"version": "1.0.3",
"model_format": "TORCH_SCRIPT"
}
以下のレスポンスが戻ります。
{
"task_id": "50t5SJkBaM51bfw44Nfk",
"status": "CREATED"
}
現在モデルをデプロイ中ですので、以下のコマンドでステータスを確認します。
GET /_plugins/_ml/tasks/<task_id>
<task_id>
は先ほど出力されたものに置き換えます。
何度か待ちながら実行を行うと以下の様にステータスが COMPLETED
になります。
{
"model_id": "6Et5SJkBaM51bfw45dfb",
"task_type": "REGISTER_MODEL",
"function_name": "TEXT_EMBEDDING",
"state": "COMPLETED",
"worker_node": [
"6EK0VEa5Q-6lpj_r3PAsFw"
],
"create_time": 1757857571043,
"last_update_time": 1757857628664,
"is_async": true
}
表示されたmodel_id
をコピーしておきます。
3. インジェストパイプラインの作成
インジェストパイプラインを作成することで、データの挿入時にインジェストプロセッサ(上記で作成したモデルを使用した埋め込み処理)が起動するようになります。
PUT /_ingest/pipeline/nlp-ingest-pipeline
{
"description": "An NLP ingest pipeline",
"processors": [
{
"text_embedding": {
"model_id": "6Et5SJkBaM51bfw45dfb",
"field_map": {
"text": "passage_embedding"
}
}
}
]
}
以下のレスポンスが戻れば成功です。
{
"acknowledged": true
}
4. ベクトルインデックスの作成
次に、作成されたインジェストパイプラインがデータ挿入時に自動で呼び出されるようにせってを行った、ベクトルインデックスを作成します。
PUT /my-nlp-index
{
"settings": {
"index.knn": true,
"default_pipeline": "nlp-ingest-pipeline"
},
"mappings": {
"properties": {
"id": {
"type": "text"
},
"passage_embedding": {
"type": "knn_vector",
"dimension": 768,
"space_type": "l2"
},
"text": {
"type": "text"
}
}
}
}
以下が戻れば成功です。
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "my-nlp-index"
}
この2行がポイントです。
"index.knn": true,
"default_pipeline": "nlp-ingest-pipeline"
このベクトルインデックスに文字列が挿入される際に、自動でインジェストパイプラインが呼びだされます。インジェストパイプラインはインジェストプロセスにより埋め込み処理が行われます。
同時に、OpenSearch の KNN プラグイン(近似最近傍検索用の拡張機能)が有効化されています。KNNは与えられた基準によって近しいベクトルを探し出す方式です。その基準とはベクトル検索でよく用いられるコサイン類似度やユークリッド距離などです。つまりKNNが有効化されていないとベクトル検索が行えません。
5. 検索用ドキュメントの投入
PUT /my-nlp-index/_doc/1
{
"text": "A West Virginia university women 's basketball team , officials , and a small gathering of fans are in a West Virginia arena .",
"id": "4319130149.jpg"
}
挿入が成功すれば以下のレスポンスが戻ります。
{
"_index": "my-nlp-index",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
GET /my-nlp-index/_doc/1
を実行すると以下の通りベクトル化された値が挿入されていることがわかります。
{
"_index": "my-nlp-index",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"passage_embedding": [
0.044915687,
-0.34105563,
0.036822733,
-0.1413907,
-0.1735508,
-0.05015649,
-0.1205625,
0.0053460365,
-0.45868877,
-0.11599321,
-0.18455544,
-0.30200416,
0.11428389,
-0.19775316,
<snip>
同様に以下を実行してデータを挿入します。
PUT /my-nlp-index/_doc/2
{
"text": "A wild animal races across an uncut field with a minimal amount of trees .",
"id": "1775029934.jpg"
}
PUT /my-nlp-index/_doc/3
{
"text": "People line the stands which advertise Freemont 's orthopedics , a cowboy rides a light brown bucking bronco .",
"id": "2664027527.jpg"
}
PUT /my-nlp-index/_doc/4
{
"text": "A man who is riding a wild horse in the rodeo is very near to falling off .",
"id": "4427058951.jpg"
}
PUT /my-nlp-index/_doc/5
{
"text": "A rodeo cowboy , wearing a cowboy hat , is being thrown off of a wild white horse .",
"id": "2691147709.jpg"
}
6. 検索テスト
以下のコマンドで検索を実行します。 model_id
は 2. モデルの登録
の作業ごとに異なる値となるので都度置き換えます。
GET /my-nlp-index/_search
{
"_source": {
"excludes": [
"passage_embedding"
]
},
"query": {
"neural": {
"passage_embedding": {
"query_text": "wild west",
"model_id": "6Et5SJkBaM51bfw45dfb",
"k": 5
}
}
}
}
wild west
という検索テキストに意味の近しい順に5つ表示されます。
{
"took": 79,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": 0.015851954,
"hits": [
{
"_index": "my-nlp-index",
"_id": "4",
"_score": 0.015851954,
"_source": {
"text": "A man who is riding a wild horse in the rodeo is very near to falling off .",
"id": "4427058951.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "2",
"_score": 0.01574885,
"_source": {
"text": "A wild animal races across an uncut field with a minimal amount of trees .",
"id": "1775029934.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "5",
"_score": 0.015177963,
"_source": {
"text": "A rodeo cowboy , wearing a cowboy hat , is being thrown off of a wild white horse .",
"id": "2691147709.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "1",
"_score": 0.013272904,
"_source": {
"text": "A West Virginia university women 's basketball team , officials , and a small gathering of fans are in a West Virginia arena .",
"id": "4319130149.jpg"
}
},
{
"_index": "my-nlp-index",
"_id": "3",
"_score": 0.011347727,
"_source": {
"text": "People line the stands which advertise Freemont 's orthopedics , a cowboy rides a light brown bucking bronco .",
"id": "2664027527.jpg"
}
}
]
}
}
検索をベクトルで行われ意味をもとに近しいものが上位に表示されます。 4. ベクトルインデックスの作成
の手順で "space_type": "l2"
と設定したため、ユークリッド距離で類似度が計算されます。