BedrockにCohereのマルチモーダル埋め込みモデルが来た！で、何に使うの？

Bedrockで提供されているCohere Embed 3が、マルチモーダルの埋め込みに対応しました。

https://aws.amazon.com/jp/about-aws/whats-new/2025/01/amazon-bedrock-multimodal-cohere-embed-3-multilingual-english/

Cohere Embed 3のマルチモーダル対応は2024/10/24に発表されており、発表時点ではCohereのAPI、Azure AI Studio、SageMakerで提供されていました。今回、Bedrockで対応しているモデルもマルチモーダル対応しました。

https://cohere.com/blog/multimodal-embed-3

:::note
新モデルの追加ではなく、同じモデルでの機能追加です。モデルIDも変更ありません。
:::

## マルチモーダル対応とは？？

マルチモーダルというのは「テキスト」と「画像」を指します。これまではテキストの埋め込みだけでしたが、今回画像も埋め込みが可能になりました。

画像の埋め込みのユースケースとして、先程のブログでは以下のように紹介されています。

1. **グラフとチャート**:視覚的な表現は、複雑なデータを理解する上で重要です。ユーザーは、ビジネス上の意思決定に役立つ適切な図表を簡単に見つけることができるようになりました。特定の洞察を説明するだけで、Embed 3 が関連するグラフとチャートを取得し、チーム全体の従業員がデータに基づく意思決定をより効率的に行えるようになります。
1. **電子商取引の製品カタログ**:従来の検索方法では、多くの場合、顧客はテキストベースの製品説明を通じて製品を見つけるしかありません。Embed 3 は、この検索エクスペリエンスを変革します。小売業者は、テキストの説明に加えて製品画像を検索するアプリケーションを構築できるため、買い物客に差別化されたエクスペリエンスを提供し、コンバージョン率を向上させることができます。
1. **デザイン ファイルとテンプレート**:デザイナーは、多くの場合、膨大なアセット ライブラリを使用して、記憶や厳密な命名規則に頼ってビジュアルを整理します。Embed 3 を使用すると、テキストの説明に基づいて特定の UI モックアップ、ビジュアル テンプレート、プレゼンテーション スライドを簡単に見つけることができます。これにより、クリエイティブ プロセスが効率化されます。

なるほど、グラフとかにも使えるんですね。

ということで今回は、表画像を対象に検証してみたいと思います。


## 検証準備

### 検証画像

動作を確認するため、以下の画像を対象にやってみました。

画像の入手先
[Introducing computer use, a new Claude 3.5 Sonnet, and Claude 3.5 Haiku](https://www.anthropic.com/news/3-5-models-and-computer-use)
[Claude 3.5 Sonnet](https://www.anthropic.com/news/claude-3-5-sonnet)
[Amazon Nova の理解モデル](https://aws.amazon.com/jp/ai/generative-ai/nova/understanding/)

| 画像 | 説明 |
| --- | --- |
| ![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/3d2adbc5-35fd-7928-c361-fbde5480a551.png) | Claude 3.5 Sonnet(V2)とClaude 3.5　Haiku発表ブログに記載のベンチマーク
| ![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/434fdd11-d313-8fbc-0ac5-61723858e6d3.png) | Claude 3.5 Sonnet発表ブログに記載のベンチマーク
| ![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/46749ca8-fd56-3aa8-762d-1c95c50bbf0b.png) | Amazon Nova ProのText Intelligenceを対象としたベンチマーク
| ![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9f8a669e-3c90-d768-1c3f-267a9eaa40ce.png) | Amazon Nova ProのVisual Intelligenceを対象としたベンチマーク
| ![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f57d791f-684b-82b6-601a-bee84694917d.png) | Amazon Nova LiteのVisual Intelligenceを対象としたベンチマーク
| ![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/5fcd360a-5a9a-7c49-93b3-b209aeb2a73d.png) | Amazon Nova LiteのText Intelligenceを対象としたベンチマーク
| ![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/71de5b2c-ec89-cd8d-67f9-030f61bc18ab.png) | Amazon Nova MicroのText Intelligenceを対象としたベンチマーク
| ![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/d48b04ee-d3e0-f9e1-0051-ac8e46661b67.png) | Amazon Nova MicroのAgentic workflowsを対象としたベンチマーク

### 検証コード

検証コードは以下のサンプルを参考にしました。

https://github.com/cohere-ai/cohere-developer-experience/blob/main/notebooks/Multimodal_Semantic_Search.ipynb

ライブラリーをインポートします。

```python
import base64
import json
import os

import boto3
import hnswlib
from IPython.display import HTML, display
from PIL import Image
```

boto3のクライアントを生成します。

```python
client = boto3.client("bedrock-runtime")
```

表の画像を読み込みます。

```python
folder_path = "./bench/"
files = os.listdir(folder_path)
```

ファイルを開き、Bedrockに送信してベクトル化（埋め込み）を行う関数を定義します。

```python
def image_to_base64_data_url(image_path):

    with open(image_path, "rb") as f:
        print(image_path)
        enc_img = base64.b64encode(f.read()).decode("utf-8")
        enc_img = f"data:image/png;base64,{enc_img}"

    response = client.invoke_model(
        modelId="cohere.embed-multilingual-v3",
        body=json.dumps(
            {
                "images": [enc_img],
                "input_type": "image",
                "embedding_types": ["float"],
            }
        ),
    )

    return json.loads(response.get("body").read())
```

:::note
ベクトル化はinvoke_modelを使用します。（converseではベクトル化はできません）
:::

ベクトル化を実行します。

```python
embeddings = []
file_paths = []

for file in files:
    file_path = os.path.join(folder_path, file)

    try:
        res = image_to_base64_data_url(file_path)
    except Exception as e:
        print(f"Failed to embed {file_path}")
        print(e)
        continue

    file_paths.append(file_path)
    embeddings.append(res["embeddings"]["float"][0])
```

ベクトルデータをIndexに登録します。

```python
index = hnswlib.Index(space="cosine", dim=1024)
index.init_index(max_elements=len(embeddings), ef_construction=512, M=64)
index.add_items(embeddings, list(range(len(embeddings))))
```

:::note
検証用の簡易ベクトルストアとして、[Hnswlib](https://github.com/nmslib/hnswlib)を使用します
:::


ベクトルストアを検索する関数を作成します。

```python
def retrieve_image(query):
    top_k = 5
    size = (200, 200)

    response = client.invoke_model(
        modelId="cohere.embed-multilingual-v3",
        body=json.dumps(
            {
                "texts": [query],
                "input_type": "search_query",
                "embedding_types": ["float"],
            }
        ),
    )
    query_emb = json.loads(response.get("body").read())["embeddings"]["float"]

    # Retrieve the initial results from your vector db
    res = index.knn_query(query_emb, k=top_k)
    doc_index = res[0][0]
    doc_scores = res[1][0]

    # Let's view the response image:
    print(
        f"The most relevant photo in the top {top_k} retreived images with a distance score of: {doc_scores[0]:.2f}"
    )
    img = Image.open(file_paths[doc_index[0]])
    img_resized = img.resize(size)
    display(img_resized)

    # For the full list of images grabbed:
    print("-" * 100)
    print("All top k images:")
    for x in range(0, len(doc_index)):
        print(f"Ranking of Relevance:{x+1} with a distance of: {doc_scores[x]:.2f}")
        img = Image.open(file_paths[doc_index[x]])
        img_resized = img.resize(size)
        display(img_resized)
```

## 検証結果

それでは、実施結果を見ていきましょう。

### **検索クエリー**：「Nova Microと他のモデルを比較した表」

| スコア（距離） | 画像 | 判定 |
| --- | --- | --- |
| 0.48 | ![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/d48b04ee-d3e0-f9e1-0051-ac8e46661b67.png) | 正解
| 0.50 | ![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9f8a669e-3c90-d768-1c3f-267a9eaa40ce.png) | 不正解
| 0.51 |  ![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/71de5b2c-ec89-cd8d-67f9-030f61bc18ab.png) | 正解
| 0.51 | ![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f57d791f-684b-82b6-601a-bee84694917d.png) | 不正解
| 0.53 | ![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/46749ca8-fd56-3aa8-762d-1c95c50bbf0b.png) | 不正解

スコアはベクトル間の距離で、数値が小さい（＝近い）ほど、意味が似ているという判定になります。
スコアが1番目のものと2番目のものにNova Microが含まれている結果となりました。なんとなく、あってそうです。

:::note
表の画像がほぼ似通ってるからか、スコアに明確な差はないですね。
:::


### **検索クエリー**：「Nova Proの画像を対象としたベンチマーク性能の表」

| スコア（距離） | 画像 | 判定 |
| --- | --- | --- |
| 0.43 | ![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9f8a669e-3c90-d768-1c3f-267a9eaa40ce.png) | 正解
| 0.44 | ![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f57d791f-684b-82b6-601a-bee84694917d.png) | 不正解
| 0.45 | ![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/d48b04ee-d3e0-f9e1-0051-ac8e46661b67.png) | 不正解
| 0.46 | ![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/46749ca8-fd56-3aa8-762d-1c95c50bbf0b.png) | 不正解
| 0.48 | ![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/71de5b2c-ec89-cd8d-67f9-030f61bc18ab.png) | 不正解

正解画像をズバッと当ててくれました。

### **検索クエリー**：「Claude 3.5 Haikuのベンチマーク結果を含む表」

| スコア（距離） | 画像 | 判定 |
| --- | --- | --- |
| 0.46 | ![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f57d791f-684b-82b6-601a-bee84694917d.png) | 正解
| 0.47 | ![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/434fdd11-d313-8fbc-0ac5-61723858e6d3.png) | 正解
| 0.49 | ![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9f8a669e-3c90-d768-1c3f-267a9eaa40ce.png) | 不正解
| 0.49 | ![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/434fdd11-d313-8fbc-0ac5-61723858e6d3.png) | 不正解
| 0.49 | ![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/5fcd360a-5a9a-7c49-93b3-b209aeb2a73d.png) | 不正解

２つ目のものを期待してましたが、１つ目の画像にもClaude 3.5 Haikuの文字があるので正解としました。

## まとめ

マルチモーダル埋め込みの使い方がなんとなくわかりました。画像を引っ張ってきて、Claude 3.5 Sonnetのようなマルチモーダル対応のテキスト生成モデルを使用したRAGができそうな感じですね。

ただ、どういう仕組みかがいまいちわからないので、画像中に文字情報がないと難しいのかもしれません。