Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

色んな生成AI触ってみたい!Amazon BedrockとAmazon OpenSearch Serviceを使った類似画像の検索。

こんにちは!オルターブースのいけだです!

この投稿はアドベントカレンダー18日目の投稿です! adventar.org

さて、今年、1年を振り返ってみると生成AIの話で持ちきりだった気がします。

生成AIの進化スピードは目まぐるしいものがありますね。 中でも火付け役となったOpenAI (Azure OpenAI)ですが、他の生成AIも気になるところです。

という事で今回は、AWSの生成AIサービスである「Amazon Bedrock」と「Amazon OpenSearch Service」を使ったベクトル検索をやってみようと思います。 テーマは「画像の類似検索」です。それでは早速始めてみましょう!

Amazon Bedrock

aws.amazon.com

Amazon Bedrock は、AI21 Labs、Anthropic、Cohere、Meta、Stability AI、Amazon などの大手 AI 企業が提供する高性能な基盤モデル (FM) を単一の API で選択できるフルマネージド型サービスです。また、生成系 AI アプリケーションの構築に必要な幅広い機能も備えているため、プライバシーとセキュリティを維持しながら開発を簡素化できます。

フルマネージドな生成AIサービスで、複数の基盤モデル(Foundation Model ) が提供され、カスタマイズや他AWSサービスとの統合が可能という事ですね。

以下は、現在提供されている基盤モデルです。

基盤モデル 概要
Amazon Titan テキストの生成と分類、質疑応答、情報抽出のためのFM。パーソナライゼーションと検索のためのテキスト埋め込みモデルも含む。
Jurassic 質疑応答、要約、テキスト生成など、あらゆる言語タスクに対応するInstruction Following FM。
Claude 思慮深い対話、コンテンツ作成、複雑な推論、創造的な活動、コーディング向けのFM。Constitutional AIおよび無害性トレーニングに基づく。
Command ビジネスユースケース向けに最適化されたテキストベースの応答を生成できるプロンプトベースのテキスト生成モデル。
Llama 2 対話のユースケースに最適な微調整されたモデル。
Stable Diffusion 画像生成モデルは、ユニークかつリアルで質の高いビジュアル、アート、ロゴ、デザインを生成します。

今回は、先日のAWS re:Invent2023で発表となった「Amazon Titan Multimodal Embeddings」というマルチモーダルモデルを使ってみようと思います。

aws.amazon.com

Titan Multimodal Embeddings を使用すると、コンテンツの埋め込みを生成してベクトルデータベースに保存できます。エンドユーザーがテキストと画像の任意の組み合わせを検索クエリとして送信すると、モデルによって、検索クエリの埋め込みの生成と保存されている埋め込みとの照合が行われ、関連する検索とレコメンデーションの結果がエンドユーザーに提供されます。

テキストに加え画像に対しても埋め込み(Embeddings)を行う事ができるようです。 現在は使えるリージョンに制限があり、米国東部 (バージニア北部) と米国西部 (オレゴン) のみで使用可能との事。

料金は現状、以下のように記載されています。

Amazon Titan models 1,000入力トークンあたり 入力画像1枚あたり
Titan Multimodal Embeddings $0.0008 $0.00006

https://aws.amazon.com/bedrock/pricing/

さっそく、バージニア北部のリージョンで有効化してみましょう。

「Manage model access」を選択し、「Titan Multimodal Embeddings G1」を有効化します。

Amazon OpenSearch Serverless

Amazon OpenSearch Serverless は、Amazon OpenSearch Service のサーバーレス構成です。 手動の調整等が必要ないため、複雑さが解消され、コスト効率が高い事も特徴です。

docs.aws.amazon.com

今回は、このAmazon OpenSearch Serverlessをベクトルストアに用います。

docs.aws.amazon.com

早速、コレクションを作成します。

コレクションタイプに「ベクトル検索」、「開発/テストモードを有効化」にして、あとはデフォルトのまま進みます。

正常に作成され、エンドポイントが確認できます。

また、「インデックス」のタブを見てみます。

まだ何も無い状態です。 インデックスを作成するには、ドキュメント記載のようにPostmanなどを使うか、もしくは「ベクトルインデックスの作成」から作成する事もできます。

今回は、以下を参考にPython(Jupyter Notebook)でインデックスの作成から画像の類似検索を試してみました。

github.com

まず、以下のようなCSVを用意しました。

title img
とろけるようなおいしさ。お寿司で大人気。名前はマグロ。 ./img/tuna.jpg
おめでたいときによく食べる。歯ごたえのあるおいしさ。名前はタイ。 ./img/tai.jpg
味噌との相性が抜群。個人的には一番すき。名前はサバ。 ./img/saba.jpg

画像は以下のような写真です。

(マグロの画像)

(タイの画像)

(サバの画像)

上記の画像をベクトル化し、似たような画像で類似検索できるかやってみようと思います。

まずインデックスを作成します。

import boto3
import json
import base64
import pandas as pd
from opensearchpy import OpenSearch, RequestsHttpConnection

AWS_ACCESS_KEY_ID="<AWS_ACCESS_KEY_ID>"
AWS_SECRET_ACCESS_KEY="<AWS_SECRET_ACCESS_KEY>"

# Bedrock
bedrock_runtime = boto3.client(
        service_name="bedrock-runtime",
        region_name="us-east-1",
        aws_access_key_id=AWS_ACCESS_KEY_ID,
        aws_secret_access_key=AWS_SECRET_ACCESS_KEY
    )

# OpenSearch
service = "aoss"  # must set the service as 'aoss'
region = "ap-northeast-1"
dimensions = 1024
index_name = "test-index"

credentials = boto3.Session(
    aws_access_key_id=AWS_ACCESS_KEY_ID,
    aws_secret_access_key=AWS_SECRET_ACCESS_KEY).get_credentials()

awsauth = AWS4Auth(
    AWS_ACCESS_KEY_ID,
    AWS_SECRET_ACCESS_KEY,
    region,
    service,
    session_token=credentials.token)

client = OpenSearch(
    hosts = "<open_search_end_point_url>",
    http_auth = awsauth,
    use_ssl = True,
    verify_certs = True,
    connection_class = RequestsHttpConnection,
    engine = "faiss",
    timeout = 300,
)

client.indices.create(index_name,
    body={
        "settings":{
            "index.knn": True
        },
        "mappings":{
            "properties": {
                "values": {
                    "type": "knn_vector",
                    "dimension": dimensions
                },
                "title": {
                    "type": "text"
                }
            }
        }
    }
)
{'acknowledged': True, 'shards_acknowledged': True, 'index': 'test-index'}

コンソールで確認すると、インデックスが作成された事が確認できます。

作成したインデックスにBedrockAmazon Titan Multimodal Embeddingsを使って画像をベクトル化しアップロードしていきます。

df = pd.read_csv("./img/data.csv")

for _, row in df.iterrows():
    title = row["title"]
    image_path = row["img"]

    with open(image_path, "rb") as image_file:
        input_image = base64.b64encode(image_file.read()).decode("utf8")

    body = json.dumps({"inputText": title, "inputImage": input_image})

    response = bedrock_runtime.invoke_model(
        body=body,
        modelId="amazon.titan-embed-image-v1",
        accept="application/json",
        contentType="application/json",
    )

    response_body = json.loads(response.get("body").read())

    vector_body = response_body.get("embedding")
    vectors = {"values": vector_body, "title": title}

    response = client.index(index=index_name, body=vectors)

コンソールを見ると、アップロードされた事が確認できます。

類似検索してみる

それでは、まず文章のベクトル検索を試してみます。 なお、今回は類似スコアが高い1件のみを返却としています。

body = json.dumps(
    {"inputText": "味噌との相性が良い魚は?"}
)

query_response = bedrock_runtime.invoke_model(
    body=body,
    modelId="amazon.titan-embed-image-v1",
    accept="application/json",
    contentType="application/json",
)

response_body = json.loads(query_response.get("body").read())
query_body = response_body.get("embedding")

search_query = {
    "size": 1,
    "query": {
        "knn": {
            "values": {
                "vector": query_body,
                "k": 1
            }
        }
    }
}

results = client.search(index=index_name, body=search_query)

for hit in results["hits"]["hits"]:
    print(hit["_source"]["title"])

結果

味噌との相性が抜群。個人的には一番すき。名前はサバ。

検索文字を変えてみます。

body = json.dumps({"inputText": "お寿司で人気なのは?"})

結果

とろけるようなおいしさ。お寿司で大人気。名前はマグロ。

文字の検索は良好です。

では、次に画像で類似検索してみます。内容をinputImage に変えます。

+ with open("query_tai.jpg", "rb") as image_file:
+    input_image = base64.b64encode(image_file.read()).decode("utf8")

- body = json.dumps({"inputText": "味噌との相性が良い魚は?"})
+ body = json.dumps({"inputImage": input_image})
...

因みに、検索対象は👇

検索で使っている画像は👇

ちゃんと「タイ」の事を返してくれるでしょうか。

結果

おめでたいときによく食べる。歯ごたえのあるおいしさ。名前はタイ。

ちゃんと類似画像から「タイ」を検索できました!🙌

もう一度試してみます。(マグロなら正解)

検索対象👇

検索で使っている画像👇

結果

とろけるようなおいしさ。お寿司で大人気。名前はマグロ。

「マグロ」も検索できたようです!👏

最後に

Multimodal Embeddingsを使用する事で画像の類似検索も簡単におこなう事ができました。 これらは様々な場面で活用できそうですね。

今回は簡単に触れてみた程度なので、さらに色々試して理解を深めていこうと思います。拙い内容で恐縮ですが、最後までご覧頂き有難うございました。

アドベントカレンダーはまだまだ続きますのでお楽しみに!

他参考にしたURL

opensearch.org

www.pinecone.io


サービス一覧 www.alterbooth.com cloudpointer.tech www.alterbooth.com