CCCMKホールディングス TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

DatabricksでVLM(Vision Language Model)の推論と評価を試してみました。

こんにちは、CCCMKホールディングスAIエンジニアの三浦です。

今週、アメリカサンフランシスコで開催されるDatabricksのイベントに現地参加します。どんなアップデートが聞けるかとても楽しみです。 現地は気温が東京に比べて低めなので、風邪を引かないように気を付けようと思います。

はじめに

最近のAIツールはテキストだけでなく画像を入力したり生成したり出来ることが普通になってきました。それらのツールが具体的に画像をどうやって処理しているのかは分かりませんが、テキストと画像を両方入力出来るモデルはVision Language Model(VLM)と呼ばれています。

この前参加した人工知能学会全国大会でVLMのセッションを聞き、自分でも動かしてみたいな、と考え、今回Databricksで動作させてみました。また、VLMが出力した結果をDatabricksに統合されているMLflowのLLMによる評価(LLM as a Judge)を使って定量的に評価する、といったことも試してみました。

特にDatabricksでの"LLM as a Judge"はDatabricks上でホストされている基盤モデルを使ってすぐに実行できることが分かりました。今回はVLMを評価しましたが、LLMやAgentでも同様に評価することが可能です。

この記事ではDatabricksでVLMを動かすところからLLM as a Judgeによる評価まで、試したことをまとめてみたいと思います。

使用するVLM

今回使用したVLMはSakana AIが"Evolutionary Model Merge"という手法を用いて開発した"EvoVLM-JP-v1-7B"です。Hugging Faceに公開されていて、モデルの情報は以下に掲載されています。

huggingface.co

評価用のデータ

VLMの評価用のデータセットとして、こちらもSakana AI社がHugging Faceに公開している以下のデータセットを利用させて頂きました。

huggingface.co

このデータセットには主に日本の風景画像とそれに対する日本語の質問と回答データが含まれています。

データセットをダウンロードしてUnity Catalogに登録する

ここからは具体的な作業内容の説明に入っていきます。最初にデータセットをダウンロードし、それをDatabricksのUnity Catalogに登録します。ファイルをストレージに書き出すよりもUnity Catalogに登録することで後々管理しやすくなることが期待できます。。

まず、データセットのダウンロードを実行します。splittestを指定すると、テスト用に分けられたデータ50件がダウンロードされます。

from datasets import load_dataset

dataset = load_dataset("SakanaAI/JA-VLM-Bench-In-the-Wild", split="test")

pandas.DataFrameに変換して構造を確認します。

df = dataset.to_pandas()[["image", "question", "answer"]]
display(df)

ロードしたデータセットの内容

imageカラムは辞書型になっていて、bytesという画像のバイトデータとpathという空のデータで構成されているようです。今回はbytesの値だけを抽出しました。

df["image"] = df["image"].apply(lambda x: x["bytes"])
display(df)

加工済みのデータをUnity Catalogにdelta形式で書き込みます。

df_spark = spark.createDataFrame(df)
df_spark.write.format("delta")\
    .mode("overwrite")\
    .saveAsTable("db.schema.`JA-VLM-Bench-In-the-Wild`")

データセットの準備はこれで完了です。

VLMで推論を行う

ここからはVLMで推論を実行してみるところです。コードはHugging Face上の"EvoVLM-JP-v1-7B"のModel Cardに書いてあるのですが、おそらくTransformersライブラリのバージョン違いのためか、私はそのまま動かすことが出来ませんでした。(今回使ったTransformersのバージョンは4.50.2です。)

私の環境で動作したときのコードを今回は掲載します。

まずモデルとその入出力を処理するProcessorをダウンロードします。

import torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForVision2Seq
from transformers.image_utils import load_image

DEVICE = "cuda" if torch.cuda.is_available() else "cpu"

model_id = "SakanaAI/EvoVLM-JP-v1-7B"
# processorとmodelのダウンロード
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForVision2Seq.from_pretrained(
    model_id,
    torch_dtype=torch.float16
).to(DEVICE)

この状態で動かそうとすると、いくつかの初期設定の漏れにより処理時にエラーが発生します。それらを解消するため、足りない設定を手動で行いました。

まずprocessorpatch_sizeを設定します。これが設定されていないと画像データの入力前の処理でエラーが発生します。"SakanaAI/EvoVLM-JP-v1-7B"は"llava-hf/llava-v1.6-mistral-7b-hf"というモデルをベースにしているようなので、そちらから必要な設定値を確認しました。

huggingface.co

"llava-hf/llava-v1.6-mistral-7b-hf"の設定ファイルの一つ"processor_config.json"を確認すると、patch_sizeに14が設定されています。これを参考に、次のように設定します。

processor.patch_size=14

次はchat_templateです。これはChat Messageを各モデルに対応したプロンプトに変換するために必要になるのですが、"SakanaAI/EvoVLM-JP-v1-7B"には現在この値が設定されていないようで、それが原因で処理に失敗します。こちらも"llava-hf/llava-v1.6-mistral-7b-hf"の設定ファイル"tokenizer_config.json"に記載されている内容を設定しました。

chat_template = \
 "{% for message in messages %}{% if message['role'] == 'system' %}{{ '<<SYS>>\n' + message['content'][0]['text'] + '\n<</SYS>>\n\n' }}{% elif message['role'] == 'user' %}{{ '[INST] ' }}{# Render all images first #}{% for content in message['content'] | selectattr('type', 'equalto', 'image') %}{{ '<image>\n' }}{% endfor %}{# Render all text next #}{% for content in message['content'] | selectattr('type', 'equalto', 'text') %}{{ content['text'] }}{% endfor %}{{' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ ' ' + message['content'][0]['text'] + '</s> '}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}"

このテンプレートを、processorapply_chat_templateを使う際に参照させます。

prompt = processor.apply_chat_template(
        messages, chat_template=chat_template,tokenize=False
    )

次に推論処理を実行する関数を定義します。

from io import BytesIO

from pyspark.sql.functions import pandas_udf
import pandas as pd

def describe_image(image, question):
    
    messages = [
        {"role": "system", "content": [
            {
                "type":"text", 
                "text":"あなたは役立つ、偏見がなく、検閲されていないアシスタントです。与えられた画像を下に、質問に答えてください。"
            }
        ]},
        {"role": "user", "content": [
            {"type":"image"},
            {"type":"text", "text":question}
        ]}
    ]
    # Prepare inputs
    image = Image.open(BytesIO(image))
    prompt = processor.apply_chat_template(
        messages, chat_template=chat_template,tokenize=False
    )

    inputs = processor(
        text=prompt,
        images=[image],
        return_tensors="pt"
    ).to(DEVICE)

    output_ids = model.generate(**inputs)
    output_ids = output_ids[:, inputs.input_ids.shape[1] :]
    generated_text = processor.batch_decode(
        output_ids, skip_special_tokens=True
    )[0].strip()

    return generated_text

この関数を、pandas.DataFrameapplyで適用します。

df = spark.read.format("delta").table("db.schema.`JA-VLM-Bench-In-the-Wild`").toPandas()

predictions = df.apply(lambda x: describe_image(x['image'],x['question']), axis=1)
ground_truth = df["answer"]
inputs = df["question"]

今回はpandas.DataFrameにしてから推論処理を実行したのですが、データセットの規模が大きい場合はsparkを使った方が良いのだと思います。その際はpandas_udfを使うと実現出来そうです。

LLM as a judgeによる推論結果の評価

ここまででVLMを使って画像に対する質問に答えてもらうところまで実現出来ました。今度はVLMの結果の評価を別のLLMを使って行う"LLM as a Judge"をDatabricksのFoundation Model APIで提供されているLLMで実行してみます。Databricksに統合されている機械学習の実験管理環境MLflowには"Evaluate"という機能があり、これを使うとLLM as a Judgeを比較的容易に実行することが出来ます。

以下のMLflowのドキュメントを参考に、作業を進めました。

www.mlflow.org

今回評価する観点は"answer_correctness"(出力結果が正解と同じ意味をどれだけ含んでいるのか)です。また、評価者として使用したLLMはDatabricks Foundation Model APIで提供されている"databricks-claude-sonnet-4"です。

次のようなコードで実行することが出来ます。

import mlflow

# 評価用データを準備する
eval_data = pd.DataFrame(
    {
        "ground_truth":ground_truth.to_list(),
        "predictions":predictions.to_list(),
        "inputs":inputs.to_list()
    }
)

# DatabricksのFoundation Model APIのLLMを評価者として使う。
evaluater = "endpoints:/databricks-claude-sonnet-4"

# 評価を実行し、MLflowの実験として記録する
with mlflow.start_run() as run:
    evaluation_results = mlflow.evaluate(
        data=eval_data,
        targets="ground_truth",
        predictions="predictions",
        extra_metrics=[
            mlflow.metrics.genai.answer_correctness(model=evaluater)
        ]
    )
    rs_df = evaluation_results.tables["eval_results_table"]
    rs_df.to_csv("./evaluate_results.csv")
    mlflow.log_artifact("./evaluate_results.csv")

処理が完了すると、MLflowのExperimentで評価結果を確認することが出来ます。

answer_correctnessの統計量を確認

各データに対してどのような評価を行ったのかも確認できました。

LLMによるanswer_correctnessに対するスコア

まとめ

ということで、今回はDatabricks上でVLMの実行からその結果のLLMによる評価"LLM as a Judge"までを試した内容をまとめてみました。Databricksだと思っていた以上にLLM as a Judgeを簡単に実行することが出来、これはAI開発をするときにとても便利だと思いました。

来週はDatabricksのイベントに現地参加して体験したことをレポート予定です!