# ビジュアルデータセットエクスプローラー

現代のデータセットには、画像や音声クリップ、さらには動画など、数百万もの非構造化データが含まれています。このようなデータセットでの最近傍探索は以下のような課題があります:1. 非構造化データ間の距離の測定は曖昧であること、2. 数十億もの距離に基づいてデータをソートするには追加の努力が必要です。幸いなことに、最初の課題は最近のCLIP (opens new window)などの研究によって取り除かれ、2つ目の課題は高度なベクトル検索アルゴリズムによって強化することができます。MyScaleは、大規模なデータセットの高性能な検索を可能にするDB+AIアプリケーション向けの統合データベースソリューションを提供しています。この例では、ハードネガティブマイニング技術を使用してファイングレインド分類器をトレーニングすることで、DB+AIアプリケーションを構築する方法を示します。

このデモでは、プレイグラウンドとして約2万5千枚の画像を含むunsplash-25kデータセット (opens new window)を採用しました。このデータセットの写真は、複雑なシーンやオブジェクトをカバーしています。

# データベースを使用する理由

データベースの役割について質問された方へ、AIの詳細について少し深く掘り下げる必要があります。従来の分類器は、実際の生活で高い精度を得るために、大量のデータ、注釈、トレーニングのトリックが必要です。これらは十分ですが、実際には正確な分類器を得るためには必要ありません。より近い予測は、最適解により速く到達するのに役立ちます。CLIPのおかげで、分類器の良い出発点を得ることができます。残された唯一のことは、類似しているが同じではない例に焦点を当てることです。これはAIの用語でハードネガティブマイニング技術と呼ばれます。そして、ここでベクトルデータベース(例:MyScale)が輝く時です。

MyScaleは、数十億のベクトルの中から高性能な検索をサポートするベクトルデータベースです。ハードネガティブマイニングのような高コストな操作は、MyScaleを使用したAIアプリケーションや研究にとって障害にはなりません。ハードネガティブを見つけるのにかかる時間はわずか数ミリ秒です。そのため、全体のファインチューニングプロセスは、ウェブページ上でわずか数回のクリックで完了することができます。

# デモの操作方法

オンラインデモ (opens new window)を試してみませんか?

# 必要なプライオリティのインストール

  • transformers: CLIPモデルの実行に使用
  • tqdm: 人間向けの美しい進捗バー
  • clickhouse-connect: MyScaleデータベースクライアント
  • streamlit: アプリを実行するためのPythonウェブサーバー
python3 -m pip install transformers tqdm clickhouse-connect streamlit pandas lmdb torch

データベースを構築する場合は、メタデータをダウンロードすることができます。

# Unsplash 25Kデータセットのダウンロード
wget https://unsplash-datasets.s3.amazonaws.com/lite/latest/unsplash-research-dataset-lite-latest.zip
# 解凍する...
unzip unsplash-research-dataset-lite-latest.zip
# 現在の作業ディレクトリに`photos.tsv000`というファイルが作成されます
# 次に、データセットからCLIPの特徴量を抽出できます

# ベクトルを使用したデータベースの構築

# データに入る

まず、Unsplash-25kデータセットの構造を見てみましょう。ファイルphotos.tsv000には、データセット内のすべての画像のメタデータと注釈が含まれています。その1行は次のようになります:

photo_id photo_url photo_image_url ...
xapxF7PcOzU https://unsplash.com/photos/xapxF7PcOzU https://images.unsplash.com/photo-1421992617193-7ce245f5cb08 ...

最初の列はこの画像の一意の識別子を示しています。次の列は、その説明ページへのURLであり、作者やその他のメタ情報を伝えています。3番目の列には画像のURLが含まれています。画像のURLは、unsplash API (opens new window)を使用して直接画像を取得するために使用できます。上記で言及されたphoto_image_url列の例は次のようになります:

と写真の作者Timothy Kolczakに特別な感謝

したがって、次のコードを使用してデータをロードします:

import pandas as pd
from tqdm import tqdm
images = pd.read_csv(args.dataset, delimiter='\t')

# MyScaleデータベーステーブルの作成

# データベースで作業する

MyScaleでテーブルを作成するには、データベースバックエンドへの接続が必要です。このページのPythonクライアントの詳しいガイドを参照してください。

SQL(Structured Query Language)に詳しい場合は、MyScaleとの作業がはるかに簡単になります。MyScaleは、構造化クエリとベクトル検索を組み合わせており、ベクトルデータベースの作成は従来のデータベースの作成とまったく同じです。以下は、SQLでベクトルデータベースを作成する方法です:

CREATE TABLE IF NOT EXISTS unsplash_25k(
        id String,
        url String,
        vector Array(Float32),
        CONSTRAINT vec_len CHECK length(vector) = 512
        ) ENGINE = MergeTree ORDER BY id;

画像のidを文字列、urlを文字列、特徴ベクトルvectorを32ビット浮動小数点数の固定長配列(次元数512)として定義しています。言い換えると、画像の特徴ベクトルは512個の32ビット浮動小数点数で構成されています。このSQLを作成した接続で実行できます:

client.command(
"CREATE TABLE IF NOT EXISTS unsplash_25k (\
        id String,\
        url String,\
        vector Array(Float32),\
        CONSTRAINT vec_len CHECK length(vector) = 512\
) ENGINE = MergeTree ORDER BY id")

# 特徴の抽出とデータベースへの挿入

CLIP (opens new window)は、異なる形式(または学術用語で言うところの「モーダル」)のデータを統一された空間にマッチングさせる人気のある手法であり、高性能なクロスモーダル検索を可能にします。たとえば、「湖のそばの家の写真」というフレーズの特徴ベクトルを使用して、類似の写真を検索したり、その逆を行ったりすることができます。

いくつかのハードネガティブマイニングのステップを使用して、ゼロショット分類器を初期化として使用することで、正確な分類器をトレーニングすることができます。クエリされた画像に近いCLIPベクトルを初期パラメータとして使用することができます。その後、ハードネガティブマイニングの部分に進むことができます:すべての類似サンプルを検索し、すべてのネガティブサンプルを除外します。以下は、単一の画像から特徴を抽出する方法を示すコード例です:

from torch.utils.data import DataLoader
from transformers import CLIPProcessor, CLIPModel
model_name = "openai/clip-vit-base-patch32"
# CLIPモデルをダウンロードするために数分かかる場合があります
model = CLIPModel.from_pretrained(model_name).to(device)
# プロセッサは画像を前処理します
processor = CLIPProcessor.from_pretrained(model_name)
# 以前のセクションでロードしたデータを使用します
row = images.iloc[0]
# 画像のURLと一意の識別子を取得します
url = row['photo_image_url']
_id = row['photo_id']
import requests
from io import BytesIO
# 画像をダウンロードして読み込みます
response = requests.get(url)
img = Image.open(BytesIO(response.content))
# 画像を前処理してPyTorch Tensorを返します
ret = self.processor(text=None, images=img, return_tensor='pt')
# 画像の値を取得します
img = ret['pixel_values']
# 特徴ベクトル(float32、512次元)を取得します
out = model.get_image_features(pixel_values=img)
# ベクトルをDBに挿入する前に正規化します
out = out / torch.norm(out, dim=-1, keepdims=True)

これで、テーブルを構築するために必要なすべてのデータを収集しました。このパズルの最後のピースは、データをMyScaleに挿入することです。詳細なINSERT句の使用方法については、SQLリファレンスを参照してください。

# テーブルに単一の行を挿入するショーケース
# ベクトルをPythonのリストに変換する必要があります
transac = [_id, url, out.cpu().numpy().squeeze().tolist()]
# ベクトルを通常のSQLとして挿入するだけです
client.insert("unsplash_25k", transac)

# クラシファイアのfew-shot学習

# クラシファイアのパラメータの初期化

前述のように、テキスト特徴を使用してクラシファイアの初期パラメータを初期化することができます。

from transformers import CLIPTokenizerFast, CLIPModel
# トークナイザーの初期化
tokenizer = CLIPTokenizerFast.from_pretrained(model_name)
# 検索したい任意のテキストを入力します
prompt = 'a house by the lake'
# テキストのトークン化と特徴の取得
inputs = tokenizer(prompt, return_tensors='pt')
out = model.get_text_features(**inputs)
xq = out.squeeze(0).cpu().detach().numpy().tolist()

テキスト特徴ベクトルを使用することで、望む画像の近似的な重心を取得し、それがクラシファイアの初期パラメータとなります。したがって、クラシファイアクラスは次のように定義できます:

DIMS = 512
class Classifier:
    def __init__(self, xq: list):
        # DIMSの入力サイズと1つの出力でモデルを初期化します
        # バイアスは無視されるため、内積の結果にのみ焦点を当てます
        self.model = torch.nn.Linear(DIMS, 1, bias=False)
        # 初期クエリ`xq`をテンソルパラメータに変換して重みを初期化します
        init_weight = torch.Tensor(xq).reshape(1, -1)
        self.model.weight = torch.nn.Parameter(init_weight)
        # 損失とオプティマイザを初期化します
        self.loss = torch.nn.BCEWithLogitsLoss()
        self.optimizer = torch.optim.SGD(self.model.parameters(), lr=0.1)

, for , and の場合、活性化関数を持つ線形クラシファイアはCLIPの類似度メトリックとまったく同じです。つまり、入力ベクトルと決定ベクトルに基づいて類似度スコアを計算します。もちろん、クエリされた画像に近いテキスト特徴をクラシファイアの初期パラメータとして使用することができます。さらに、BCEwithLogitsLoss(バイナリクロスエントロピー損失)は、ネガティブサンプルを遠ざけ、決定ベクトルをポジティブサンプルに引き寄せるためのものです。これにより、AIの部分で何が起こっているかを直感的に理解することができます。

# データベースを使用して類似サンプルを取得する

最後に、MyScaleのパワーを使ってDB内の最近傍を瞬時に取得することができます。このアプリの最後のステップは、ユーザーの監視に基づいてクラシファイアを調整することです。

このブログではUIデザインのステップを省略します。それは物語性がありすぎて、このブログに書くには長すぎます 😛 モデルのトレーニングが行われるポイントに直接進みます。

-- We create a vector index vindex on the vector column
-- with the parameter of `metric` and `ncentroids`
ALTER TABLE unsplash_25k ADD VECTOR INDEX vindex vector TYPE MSTG('metric_type=Cosine')

ベクトルインデックスが構築された後、distanceオペレーターを使用して最も近い隣人を検索することができます。

-- Please note that the query vector should be converted to a string before being executed
SELECT id, url, vector, distance(vector, <query-vector>) AS dist FROM unsplash_25k ORDER BY dist LIMIT 9

ご注意ください:SELECTのように値を返すSQL動詞を使用する場合、結果を取得するためにclient.query()を使用する必要があります。

また、不要な行をフィルタリングする混合クエリも実行できます:

SELECT id, url, vector, distance(vector, <query-vector>) AS dist
        FROM unsplash_25k WHERE id NOT IN ('U5pTkZL8JI4','UvdzJDxcJg4','22o6p17bCtQ', 'cyPqQXNJsG8')
        ORDER BY dist LIMIT 9

上記のSQL文を文字列qstrとして名付けたと仮定すると、Pythonでのクエリは以下のようになります:

q = client.query(qstr).named_results()

返されたqには、複数の辞書のようなオブジェクトが含まれています。この場合、トップ9の最も近い近隣を要求したため、9つの返されたオブジェクトがあります。qの各要素から値を取得するには、列名を使用できます。例えば、すべてのidとクエリベクトルへの距離を取得したい場合、Pythonで次のようにコーディングできます:

id_dist = [(_q["id"], _q["dist"]) for _q in q]

# 分類器のファインチューニング

MyScaleの力を借りて、今ではDB内の最も近い隣人を一瞬で取得することができます。このアプリの最終ステップは、ユーザーの監督に関して分類器を調整することになります。

UIデザインステップは、このブログに書くにはあまりにも物語性があるため省略します 😛 モデルトレーニングが行われる時点に直接進みます。

# 注意:これを以前のクラシファイアに追加してください
def fit(self, X: list, y: list, iters: int = 5):
# Xとyをテンソルに変換します
X = torch.Tensor(X)
y = torch.Tensor(y).reshape(-1, 1)
for i in range(iters):
    # 勾配をゼロにします
    self.optimizer.zero_grad()
    # 推論前に重みを正規化します
    # これにより、勾配が制約されます。クエリベクトルで爆発する可能性があります
    self.model.weight.data = self.model.weight.data / torch.norm(self.model.weight.data, p=2, dim=-1)
    # 順伝播
    out = self.model(X)
    # 損失の計算
    loss = self.loss(out, y)
    # 逆伝播
    loss.backward()
    # 重みの更新
    self.optimizer.step()

上記のコードは、既存のクラシファイアをトレーニングするfew-shot学習パイプラインを提供します。わずかな数の画像に注釈を付けるだけで、クラシファイアは収束し、マインドのコンセプトに対して印象的な精度を提供します。

トレーニングプロセスは簡単です。まず、重みベクトルは一般的にクエリと望ましいものとの類似度を測定する指標です。クエリと望ましいものの間の角度を表す円錐または球の重心として考えることができます。クエリベクトルとスコアの閾値をその半径として扱います。円錐の内部はポジティブとして扱われ、外部はネガティブとして扱われます。トレーニングステップでは、ベクトルをできるだけ多くのポジティブにカバーし、ネガティブから遠ざけるようにします。円錐ベクトル理論を続けると、重心を記述するために正規化されたベクトルだけが必要です。したがって、学習後のパラメータを正規化する必要があります。また、もう一つの考え方として、ポジティブは正規化されていないベクトルであり、重心をその位置に引っ張ります。結果として、大きな大きさを持つベクトルが得られますが、ポジティブの間の方向をうまく表現できません。これは類似度検索のパフォーマンスを低下させます。ベクトルを正規化すると、勾配の垂直成分のみが残ります。これにより、デモの視覚的な結果が安定します。

# 最後に

このデモでは、MyScaleを使用してfew-shot学習されたクラシファイアをトレーニングするデモの構築方法を確認しました。さらに重要なことは、MyScaleを使用してデータを格納し、インデックスを作成し、拡張SQLを使用して高度なベクトル検索エンジンを使用する方法も紹介しました。このブログをお楽しみいただければ幸いです!

参考文献:

  1. MultiLingual CLIP: https://huggingface.co/M-CLIP/XLM-Roberta-Large-Vit-B-32 (opens new window)
  2. CLIP: https://huggingface.co/openai/clip-vit-base-patch32 (opens new window)
  3. Unsplash 25K Dataset: https://github.com/unsplash/datasets (opens new window)