【Azure】CosmosDBにおけるベクトル検索入門ガイド【初心者向け】

 

こんにちは、サイオステクノロジーの佐藤 陽です。

相変わらずCosmosDBについての勉強しているので、引き続きアウトプットしていきたいと思います。
今回のテーマはCosmosDBでのベクトル検索についてです。

  • ベクトル検索って何?
  • CosmosDBでどうベクトル検索を実装するの?
  • RAGにCosmosDBを組み込みたい!

といった方は是非最後までご覧ください!

はじめに

2024年のMicrosoft IgniteにてCosmosDBのベクトル検索機能がGAとなることが発表されました。
同年のMicrosoft BuildではまだPublic Previewでしたが、これで本番環境でも安心して利用できます。

このベクトル検索ですが、昨今の生成AIのアプリケーションと非常に相性が良いです。
特にRAG(Retrieval Augmented Generation)ではベクトル検索がよく使われており、CosmosDBをRAGに導入するといったパターンが今後主流になってくるのではないかと思います。

そこで本記事では、「ベクトル検索とは?」といった点からCosmosDBでのベクトル検索の実装まで一通りご紹介したいと思います。

余談

本記事はJAZUG Shizuokaでの登壇資料をベースとしているため、内容がやや静岡寄りのものとなっています。
そして是非、JAZUG Shizuokaが開催するイベントへも参加いただければと思います!

ベクトル検索とは?

ではまずベクトル検索についてご紹介します。

生成AIを活用するうえでベクトルは非常に重要な要素です。

高校生の時にベクトルとは「大きさ・向きを持つもの」として習い
X,Y平面上に矢印を書いた図をたくさん見たかと思いますが、まさにあれです。

今回の記事においては、文章や画像などをベクトル化します。

例えば「犬」「猫」「花」の3つの単語をベクトル化すると、それぞれ以下のような値で表現されます。
(ベクトル値としては1000を超える次元数であるため一部のみ掲載します)

ベクトル値 [-0.019726261, -0.014364496, -0.011311122,…] [-0.013889334, -0.050191004, -0.03891719, …] [0.02033748, 0.027342353, 0.017839076, …]

そしてこのベクトル値の特徴として、似たような使われ方をする単語は似たような値を取ることが言われています。

例えば

  • 私は毎朝、犬にエサをあげる
  • 私は毎朝、猫にエサをあげる

といったような使い方はしますが

  • 私は毎朝、花にエサをあげる

といった使い方はしませんよね。
このように、同じような使われ方をする単語は、似たようなベクトル値を持ちます。

今回はこの「似たような値」を評価するためにコサイン類似度を利用します。
犬, 猫, 花の3つの単語のベクトルア値、それぞれの組み合わせでコサイン類似度を算出した値を以下に示します。

犬&猫 犬&花 猫&花
0.6180 0.4022 0.3522

この結果から、「犬」と「猫」のベクトル値の相関が高くなり、近しい意味を持つことを表しています。
人間の感覚からしたら明らかな結果ですが、コンピューターが定量的な値としてこの意味の近さを扱えることは大きなメリットです。

この仕組みを利用したのがベクトル検索です。
ベクトル検索は、ユーザーからの質問(クエリ)をベクトル化し、データベースなどから似たようなベクトル値を持つものを算出します。
これにより、キーワード的には一致していないとしても、意味的に近しいものをデータベースから取得することが可能となります。

CosmosDBでのベクトル検索

では実際にCosmosDBにおけるベクトル検索の例を見てみたいと思います。

まず初めに検索対象となるデータをCosmosDBに格納します。

今回は、静岡の観光地をテーマに格納するデータを用意しました。

観光地名 説明
富士山 世界文化遺産に登録された、標高3,776メートルの日本一高い象徴的存在の山。四季を通じて登山や観光で多くの人が訪れ、山頂からの日の出は特に有名。
伊豆半島 地中海のような青い海と豊富な自然が魅力的なリゾート地。温泉や海水浴、ダイビングなどのマリンスポーツが楽しめる。また、伊豆七島を望む絶景ポイントも多数存在する。
三島スカイウォーク 日本最長の歩行者専用吊り橋で、全長400メートル。橋の上からは富士山や駿河湾の壮大な景色を一望でき、特に晴れた日には絶景が広がる。
熱海温泉 江戸時代から続く歴史ある温泉地。海辺に多くの旅館が立ち並び、海を眺めながらの露天風呂が楽しめる。また、芸術作品や熱海の花火大会も観光の大きな魅力の一つ。
日本平 静岡市内からロープウェイでアクセス可能な展望公園。富士山や駿河湾の大パノラマを背景に、季節ごとの花々が美しい。
静岡県立美術館 静岡市内に位置し、地元出身の画家、レオナール・フジタの作品を中心に展示。また、世界的に著名な画家の作品も常設展示され、アートファンには見逃せないスポット。
清水エスパルスドリームプラザ 清水区にある大型のショッピングモールで、レストラン、ショップ、映画館、水族館などが集まる。家族連れで一日中楽しめる。
浜松城公園 浜松市にある城址公園で、徳川家康ゆかりの地。春には桜が咲き誇り、多くの花見客で賑わう。城内には歴史資料館もあり、歴史好きにはたまらない。
久能山東照宮 富士山の麓、久能山の山頂に鎮座する神社で、徳川家康を祀っている。豪華絢爛な彫刻や装飾が施された建物は国宝に指定されており、日本の伝統美を感じさせる。
沼津港深海水族館 沼津港に隣接する水族館で、深海に生息する珍しい生き物たちを展示。普段はなかなか見ることのできない深海生物の神秘的な世界を体験できる。

この項目に対して以下の処理を行います。

  1. ベクトル化
  2. CosomosDBへの登録

ベクトル化

まずはそれぞれのコンテンツをベクトル化します。
今回は観光地名説明の2列に分かれているものを、一つの文字列としてベクトル化していきます。

ベクトル化するにあたってはEmbeddingモデルが必要となるので事前にデプロイしておきましょう。
今回はAzure OpenAI Service上で、text-embedding-3-smallのモデルをデプロイしておきました。

そのうえで今回は以下のような実装を行いました。

url = "https://{AOAIのエンドポイント}/openai/deployments/{Embeddingのモデル名}/embeddings?api-version=2023-05-15"

content = [
"富士山 - 世界文化遺産に登録された、標高3,776メートルの日本一高い象徴的存在の山。四季を通じて登山や観光で多くの人が訪れ、山頂からの日の出は特に有名。",
"伊豆半島 - 地中海のような青い海と豊富な自然が魅力的なリゾート地。温泉や海水浴、ダイビングなどのマリンスポーツが楽しめる。また、伊豆七島を望む絶景ポイントも多数存在する。",
"三島スカイウォーク - 日本最長の歩行者専用吊り橋で、全長400メートル。橋の上からは富士山や駿河湾の壮大な景色を一望でき、特に晴れた日には絶景が広がる。",
"熱海温泉 - 江戸時代から続く歴史ある温泉地。海辺に多くの旅館が立ち並び、海を眺めながらの露天風呂が楽しめる。また、芸術作品や熱海の花火大会も観光の大きな魅力の一つ。",
"日本平 - 静岡市内からロープウェイでアクセス可能な展望公園。富士山や駿河湾の大パノラマを背景に、季節ごとの花々が美しい。",
"静岡県立美術館 - 静岡市内に位置し、地元出身の画家、レオナール・フジタの作品を中心に展示。また、世界的に著名な画家の作品も常設展示され、アートファンには見逃せないスポット。",
"清水エスパルスドリームプラザ - 清水区にある大型のショッピングモールで、レストラン、ショップ、映画館、水族館などが集まる。家族連れで一日中楽しめる。",
"浜松城公園 - 浜松市にある城址公園で、徳川家康ゆかりの地。春には桜が咲き誇り、多くの花見客で賑わう。城内には歴史資料館もあり、歴史好きにはたまらない。",
"久能山東照宮 - 富士山の麓、久能山の山頂に鎮座する神社で、徳川家康を祀っている。豪華絢爛な彫刻や装飾が施された建物は国宝に指定されており、日本の伝統美を感じさせる。",
"沼津港深海水族館 - 沼津港に隣接する水族館で、深海に生息する珍しい生き物たちを展示。普段はなかなか見ることのできない深海生物の神秘的な世界を体験できる。"
]

headers = {
  'Content-Type': 'application/json',
  'api-key': '{api-key}'
}
results = []

for spot in content:
    payload = json.dumps({
      "input": spot
    })
    response = requests.request("POST", url, headers=headers, data=payload)
    response_data = response.json()
    embedding = response_data['data'][0]['embedding']
    result = {
        "content": spot,
        "contentVector": embedding
    }
    results.append(result)
ここでresultとして出力された内容を一つ見てみます。
{
    "content": "富士山 - 世界文化遺産に登録された、標高3,776メートルの日本一高い象徴的存在の山。四季を通じて登山や観光で多くの人が訪れ、山頂からの日の出は特に有名。",
    "contentVector": [
        0.015276342,
        -0.04262919,
        -0.010207734,
        0.027943453,
        0.033972893,
        ...
    ]
}

このように、元の情報(content)と、その値をベクトル化した値(contentVector)が用意できました。

CosmosDBへの登録

事前準備

ではこの用意出来た値をCosomosDBに登録してきます。
CosomosDBにベクトル値を登録するには事前準備が必要となるため、以下に手順を示します。

コンテナ作成時にベクトルポリシーを追加します。

以下が設定項目です。

パラメータ 概要
Path ベクトル値として扱うパラメータのパスです。今回は/contentVectorが対象となります。
Data データのタイプです。今回扱うEmbeddingモデルがfloat32で出力するため、これに合わせてfloat32に設定します。
Distance function ベクトル間の距離を測るための関数です。こちらは先程ご紹介したコサイン類似度を利用します
Dimensions ベクトルの次元数です。こちらは今回扱うEmbeddingモデルの次元数が1536となっているため、この値を設定します。
Index type インデックス方式です。このあたりは検索精度に関わってきますが、今回は詳細な説明は割愛します。diskANNとします

コンテナ作成完了後、Settingsファイルを確認します。
先程設定した内容が正しくポリシーとして登録されていることが確認できます。

データアップロード

準備が出来たところで、こちらのコンテナに先程のデータを格納していきます。
今回は、先程のベクトル化の処理の最後に出力結果をjsonファイルとして出力しておき、その内容を以下のコードで改めてアップロードします。

# CosmosDBの接続情報
endpoint = "{CosnosDBのエンドポイント}"
key = "{Key}"
database_name = "sios-database"
container_name = "vector-container"

# CosmosDBクライアントの作成
client = CosmosClient(endpoint, key)
database = client.get_database_client(database_name)
container = database.get_container_client(container_name)

# JSONファイルをアップロードする関数
def upload_json_to_cosmosdb(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
        # idフィールドを追加
        data['id'] = data['content']
        container.upsert_item(data)

# 先程出力したベクトル値を含むJSONファイルをアップロード
# 今回は簡易的に`result_`から始まるファイルを指定
for file_name in os.listdir('.'):
    if file_name.startswith('result_') and file_name.endswith('.json'):
        upload_json_to_cosmosdb(file_name)
        print(f"Uploaded {file_name} to CosmosDB")


print("All files have been uploaded.")

このコードを実行した後にCosmosDBのデータエクスプローラを見てみると、無事にデータが登録されていることが確認できました。

ベクトル検索

次に格納されたデータに対して検索を行っていきます。

今回は以下のような検索を行ってみたいと思います。

「景色が良い所は?」

まずは、この質問(クエリ)をベクトル化します。
先程と同様にEmbeddingモデルを利用してベクトル化したところ以下のような結果となりました。

[−0.01532, −0.02434, −0.05363, ...]

この値を利用して検索を行います。
検索クエリとしては以下の通りです。

-- データベース内のコレクション "c" から、最も類似した10件のデータを取得するクエリ
SELECT TOP 10
    c.content, -- 各ドキュメントの "content" フィールドを取得
    VectorDistance(
       c.contentVector, -- ドキュメント内のベクトルデータ
       [0.0015812921, -0.01822265, -0.011891554,...] -- クエリで指定したターゲットベクトル
    ) AS SimilarityScore -- ベクトル間の距離を "SimilarityScore" として取得
FROM c
-- ベクトル間の距離(類似度)で昇順に並べ替え
ORDER BY VectorDistance(c.contentVector, [0.0015812921, -0.01822265, -0.011891554,...] )

これにより、CosmosDB上から最も類似したデータを検索することが可能です。
実行した結果を以下に示します。

順位 コンテンツ 類似度スコア
1 日本平 – 静岡市内からロープウェイでアクセス可能な展望公園。富士山や駿河湾の大パノラマを背景に、季節ごとの花々が美しい。 0.5055713624352297
2 伊豆半島 – 地中海のような青い海と豊富な自然が魅力的なリゾート地。温泉や海水浴、ダイビングなどのマリンスポーツが楽しめる。また、伊豆七島を望む絶景ポイントも多数存在する。 0.3731433864334767
3 久能山東照宮 – 富士山の麓、久能山の山頂に鎮座する神社で、徳川家康を祀っている。豪華絢爛な彫刻や装飾が施された建物は国宝に指定されており、日本の伝統美を感じさせる。 0.3712187302734037
4 三島スカイウォーク – 日本最長の歩行者専用吊り橋で、全長400メートル。橋の上からは富士山や駿河湾の壮大な景色を一望でき、特に晴れた日には絶景が広がる。 0.37086192839082927
5 熱海温泉 – 江戸時代から続く歴史ある温泉地。海辺に多くの旅館が立ち並び、海を眺めながらの露天風呂が楽しめる。また、芸術作品や熱海の花火大会も観光の大きな魅力の一つ。 0.34883694499412393
6 浜松城公園 – 浜松市にある城址公園で、徳川家康ゆかりの地。春には桜が咲き誇り、多くの花見客で賑わう。城内には歴史資料館もあり、歴史好きにはたまらない。 0.3407391717527575
7 富士山 – 世界文化遺産に登録された、標高3,776メートルの日本一高い象徴的存在の山。四季を通じて登山や観光で多くの人が訪れ、山頂からの日の出は特に有名。 0.31416708979179037
8 静岡県立美術館 – 静岡市内に位置し、地元出身の画家、レオナール・フジタの作品を中心に展示。また、世界的に著名な画家の作品も常設展示され、アートファンには見逃せないスポット。 0.30245339778589064
9 清水エスパルスドリームプラザ – 清水区にある大型のショッピングモールで、レストラン、ショップ、映画館、水族館などが集まる。家族連れで一日中楽しめる。 0.2622979141244738
10 沼津港深海水族館 – 沼津港に隣接する水族館で、深海に生息する珍しい生き物たちを展示。普段はなかなか見ることのできない深海生物の神秘的な世界を体験できる。 0.17702736397602103

このように、各観光地の概要の中に、「景色が良い」といった直接的なワードは含まれていないにも関わらず、景色がよさそうな観光地が上位の検索結果に表れました。
これは以下に示すような「コンテンツに含まれている文章のベクトル値」と、「”景色が良い所は?”の文章のベクトル値」が近い値を取った結果であると予想されます。

  • 富士山や駿河湾の大パノラマを背景に、季節ごとの花々が美しい。
  • 伊豆七島を望む絶景ポイントも多数存在する。
  • 富士山の麓、久能山の山頂に鎮座する神社

これにてベクトル検索が完了です!
何となくイメージはつかめていただけたのではないでしょうか?

注意点

ベクトル検索で扱うベクトル値は、膨大な学習データに基づいて算出されています。
そのため、一般的な単語に対するベクトル値はある程度正確に算出することができますが、ユニークな単語などは苦手です。

例えば社内で「SIOS Innovation Program」といったオリジナルの制度があったとし、通称「SIP」と呼ばれているとします。
この場合、SIPの制度に関連した情報についてベクトル検索を行おうとした場合、期待した結果が得られない場合があります。
これは、SIPもしくはSIOS Innovation Programが一般的な単語ではなく、適切にベクトル化が行えないためです。
こういったケースはベクトル検索よりも、キーワード検索が適していると言われています。

このように必ずしもベクトル検索が優れているわけではなく、ケースに応じて検索方法を切り替える必要があります。
そして、このキーワード検索とベクトル検索を組み合わせたハイブリッド検索といった強力な検索手法も活用されているのですが
2025年のBuildにてCosmosDBでもベクトル検索の機能がGAされるとうわさされています。

また実際にGAされたら試してご紹介したいと思うので期待ください!

おわり

本日はCosmosDBのベクトル検索の機能をご紹介しました。
CosmosDBでのベクトル検索機能がGAされたことにより、より生成AIソリューションとの親和性が高まり、今後の活用が期待されます。
また、ハイブリッド検索機能のGAも噂されていることにより、より一層の期待ができます。
これを機会にCosmosDBを是非活用してみてください!
ではまた!

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です