0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Azure AI Search にバイナリ ベクトル型のデータを追加してベクトル検索する

Last updated at Posted at 2024-05-29

Microsoft Build 2024 でのアップデートで Azure AI Search においてバイナリ ベクトル型での保存・検索が可能になりました。今回は Azure Machine Learning/Azure AI Studio のモデルカタログを使用して Cohere-embed-v3-multilingual をデプロイして Azure AI Search でバイナリ ベクトル検索を実施します。

デプロイ

Cohere-embed-v3-multilingual でバイナリ ベクトルを取得

response = co.embed(
    texts=[text], 
    input_type=input_type,
    embedding_types=["ubinary"]
)
response.embeddings.ubinary[0] 

embedding_types=["ubinary"] と指定することで、uint8 にパックされたバイナリ ベクトルが取得できます。

array([218, 123,  28,  17,  74,  23, 231, 217, 255, 214,  10, 218, 129,
       239, 189,  99, 114, 159, 251,  38,  28, 104, 124, 236, 107, 179,
        69,  72, 105, 173,  64, 183, 210,  74, 117, 176, 173, 182,  57,
       241,  16, 247,  28,  27,   4, 135, 128,   2, 207, 230, 139,   9,
        67,  46, 248, 103,   8,  40,  45,  51,  71, 150, 142,  49,  78,
       113,  68, 177, 132, 254, 152, 128, 131,  32, 215,  87, 174, 201,
        31, 131,  32, 122, 221, 187, 245,  84,  22, 204, 236, 192,  40,
        94,  46,  98,  79,  89,   9,  67, 191, 170, 153, 219, 216,  64,
       166, 144,  88, 197, 211,   8,  63,  21, 129,  92, 123,  74,  24,
        44,  79,   6, 102,  81, 239, 111,  67, 158, 104,  38])

この長さ 128 の uint8 配列をそのまま Azure AI Search に投入できます。

Cohere Embeddings モデルの仕様

バイナリ Embeddings は、1024 個の float32 値を 1024 ビット値に変換し、メモリを 32 分の 1 に削減します。1024 ビットの転送と保存は非効率であるため、これらの 1024 ビット値は 128 バイトにパックされ、符号付き int8 値または符号なし uint8 値として取得できます。

image.png
(https://cohere.com/blog/int8-binary-embeddings)

この手法を用いれば、OpenAI の Embeddings モデルもバイナリベクトル化できます。

【参考】 ビットのパックとは

ビットの圧縮手法として、Packbits という手法が使えます。2 値配列 (要素が 0, 1 のみの配列) の要素を、uint8 配列のビットに詰め込みます。解凍には unpackbits が使えます。

import numpy as np

array = np.array([0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0], dtype=np.uint8)
packed_bin = np.packbits(array)
packed_bin

array([ 0, 255, 128], dtype=uint8)

インデックス作成

Azure AI Search の Collection(Edm.Binary) フィールドは、1 次元あたり 1 ビットのバイナリ ベクトルを値あたり 8 ビットの uint8 値でパックして保持します。たとえば、元の埋め込みの次元が 1024 である場合、パックされたバイナリ ベクトルの長さは ceiling(1024 / 8) = 128 になります。

fields

typeCollection(Edm.Binary)vectorEncodingpackedBit に設定する必要があります。dimensions に注意してください。実際の配列の要素数ではありません。解凍後の次元数です。

"fields": [ 
    { 
      "name": "Id", 
      "type": "Edm.String", 
      "key": true, 
      "searchable": true 
    }, 
    { 
      "name": "my-binary-vector-field", 
      "type": "Collection(Edm.Byte)", 
      "vectorEncoding": "packedBit", 
      "dimensions": 1024, 
      "vectorSearchProfile": "myHnswProfile" 
    } 
]

algorithms

バイナリ ベクトル フィールドの場合、ベクトル比較はハミング距離 hamming を使います。アルゴリズムは HNSW および Exhaustive-KNN で使用できます。

   "algorithms": [
      {
        "name": "vector-config-1716115650482",
        "kind": "hnsw",
        "hnswParameters": {
          "metric": "hamming",
          "m": 4,
          "efConstruction": 400,
          "efSearch": 500
        },
        "exhaustiveKnnParameters": null
      }
    ],

ハミング距離(Hamming Distance)は、2 つの同じ長さの文字列(またはビット列)の間で、対応する位置の文字が異なる数を表す指標です。具体的には、2 つの文字列の各位置を比較し、異なる文字の数を数えます。

a = np.array([1, 1, 0, 1])
b = np.array([1, 0, 1, 1])
np.count_nonzero(a != b)

2

vectorizer

vectorizer を指定すれば自動的に Embeddings に変換してベクトル検索できます。

"vectorizers": [
  {
    "name": "vector-1716106525833-vectorizer",
    "kind": "customWebApi",
    "azureOpenAIParameters": null,
    "customWebApiParameters": {
      "httpMethod": "POST",
      "uri": "[FunctionURI]/api/embeddings?embedding_types=ubinary&code=xxx",
      "timeout": "PT3M50S",
      "authResourceId": "",
      "httpHeaders": {},
      "authIdentity": null
    },
    "aiServicesVisionParameters": null,
    "amlParameters": null
  }
],

インデックス登録

REST API は 2024-05-01-previewバージョンの CreateOrUpdateIndex を使用。Python SDK は 11.6.0b4 を使用して直接挿入しました。

ベクトル検索

vectorizer 使用

vectorizer をセットした場合は、VectorizableTextQuery にクエリーテキストを指定して検索できます。

query = "ABC"
vector_query = VectorizableTextQuery(text=query, k_nearest_neighbors=top, fields=vector_field, exhaustive=False)

docs = search_client.search(
    search_text="",
    vector_queries= [vector_query],
    select=["text,docid"],
) 

ベクトル配列

従来通り配列を与える場合は VectorizedQuery を用います。

vector = [26, 126, ...]
vector_query = VectorizedQuery(kind="vector", vector=vector, k_nearest_neighbors=top, fields=vector_field)

docs = search_client.search(
    search_text="",
    vector_queries= [vector_query],
    select=["text,docid"],
) 

検索スコア

@search.score にはハミング距離 hamming が使われますが、0.007194245 のような値が返ります。これは cosine と同様、1 / (1 + distance) のスケーリングが掛けられていますね。これはハミング距離が増加するとスコアが単調に減少するようなスケーリング変換です。自分でベクトルを unpackbits して計算したハミング距離を 1 / (1 + distance) することで等しくなることを確認できます。

閾値の設定

ハミング距離に閾値を設定するには、以下のように変換すれば分かりやすいですね。

def score_to_hamming_distance(x):
   if x <= 0:
       raise ValueError("x must be greater than 0")
   return (1 - x) / x

精度比較

前回同様、miracl 日本語 QA データ 8,066 件による比較。

float32 int8 binary
#1 Recall@3 avg 0.652 0.668 0.672
#1 Recall@5 avg 0.786 0.786 0.751
#2 Recall@3 avg 0.686 0.696 0.649
#2 Recall@5 avg 0.817 0.813 0.778

#1 Recall@3 以外はバイナリにすると精度が落ちます。これは大幅なベクトルメモリーの削減やパフォーマンス向上とのトレードオフですね。ただバイナリはリランクと組み合わせる手法によって精度向上も可能なので、こちらも試したいです。

パフォーマンス比較

制限

現在はバイナリ ベクトル型へのスカラー量子化機能やリランク機能は実装されていません。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?