🩻

MedSigLIPの使い方と機械翻訳した日本語ラベルの内視鏡画像データセットを用いたファインチューニング

に公開

はじめに

こんにちは.修士1年のYukiです.普段はAIの医療応用に関心を持っており,研究開発に取り組んでいます.大学院では医療自然言語処理の研究を行っています.

近年,MedGemmaやHealthBenchなど,大規模な言語モデル(LLM)の医療応用に関する研究が盛んに行われています.日本語の医療LLMにおいても,Zennの過去記事「日本語の医療ドメインに特化したLLMの現状とこれから」で紹介されているように,多くのモデルが登場しています.直近では,医療LLMの開発事例として,ELYZA-LLM-Medの報告や,オープンソースのLLMでHealthBenchでGPT-4.5やo4-miniを性能とコストの両面で超えるII-Medical-8B-1706[1]などもあります.

しかし,これらのモデルの多くはテキストが中心であり,マルチモーダルモデルに関する報告は限られています.さらに,MedGemmaのような画像を含むモデルであっても放射線画像などが主で,学習が不十分な分野も多くあります.例えば,「内視鏡検査」の画像データセットで学習されているものはまだ多くありません.

そこで本記事では,「オープンソースの医療向けマルチモーダルモデルを手元のデータで手軽に学習させたら,どの程度の性能を発揮できるのか?」という問いを検証します.また,MedSigLIPの使い方も紹介します.

具体的には,Googleが2025年7月に公開したMedSigLIPモデルをファインチューニングし,特に日本語テキストと画像のペアで学習させた際のプロセスと結果について紹介します.
本記事は,以下のような方を対象としています.

  • MedSigLIPのような最新の医療マルチモーダルモデルを手元のデータで試してみたい方
  • MedSigLIPのzero shotから学習,評価までのやり方が気になる方

背景

MedSigLIPとは

MedSigLIPは,SigLIP(Sigmoid Loss for Language Image Pre-training)の派生版であり,Googleから公開された医療画像と言語を結びつけるための基盤モデルです.様々な医療画像タスクに対応可能な柔軟性を持っています.このモデルは,胸部X線写真,皮膚科画像,眼科画像,組織病理スライド,CT/MRI等の医療画像とテキストのペアを用いて学習されております.より詳しい情報については,以下の公式ページをご参照ください.Google Health AI Developer Foundations - MedSigLIP

今回このモデルを選んだ理由は,同じくGoogleから公開されているMedGemmaと並び,オープンソースで利用可能な医療用マルチモーダルモデルとして最新かつ高い性能が報告されているからです.手元の環境でも検証しやすく,モデルサイズも含めてカスタムデータでのファインチューニングに適しています.

外科手術領域とLLM/VLM

外科手術領域におけるVision & Languageモデル(VLM)の研究も近年進んでいます.関連するデータセットや研究は以下のリポジトリにまとめられており,活発な研究分野であることがわかります.

本記事での取り組みは,こうした流れの中で,汎用的な医療VLMがどの程度,外科手術(今回は代用として内視鏡画像)という特定ドメインに対応できるかを検証する位置づけとなります.

MedSigLIPのコードの解説

以下では,トグル内にコードについて詳細に解説しておりますので,適宜ご確認ください.

MedSigLIPのコードの使用方法

MedSigLIPの使用方法

この章では,以下のnotebookについてコード解説します.このnotebookは,
実験したファイルについては,このコードをベースに書いております.

モデルのロード

Hugging Face Hubで公開されている学習済みのMedSigLIPモデルをロードします.以下のコードを実行するだけで,モデル本体と,データをモデルが扱える形式に変換するための「プロセッサ」を簡単にダウンロードできます.

import torch
from transformers import AutoProcessor, AutoModel

model_id = "google/medsiglip-448"

model = AutoModel.from_pretrained(model_id)
processor = AutoProcessor.from_pretrained(model_id)

データセットの準備

大腸がんの組織画像を集めた公開データセット「NCT-CRC-HE-100K」を使用します.このデータセットを使って,MedSigLIPをさらに賢くしていきます.
まずは,データセットをダウンロードし,zipファイルを解凍します.(15分ほどかかる場合があります)
この記事では,使用していませんが,試してみるといいと思います.

! wget -nc -q "https://zenodo.org/records/1214456/files/NCT-CRC-HE-100K.zip"
! unzip -q NCT-CRC-HE-100K.zip

次に,datasetsライブラリを使ってデータを読み込み,学習用(9,000件)と検証用(1,000件)に分割します.

from datasets import load_dataset

train_size = 9000  # @param {type: "number"}
validation_size = 1000  # @param {type: "number"}

data = load_dataset("./NCT-CRC-HE-100K", split="train")
data = data.train_test_split(
    train_size=train_size,
    test_size=validation_size,
    shuffle=True,
    seed=42,
)
# Use the test split as the validation set
data["validation"] = data.pop("test")

# Display dataset details
data

出力結果

DatasetDict({
    train: Dataset({
        features: ['image', 'label'],
        num_rows: 9000
    })
    validation: Dataset({
        features: ['image', 'label'],
        num_rows: 1000
    })
})

データは画像(image)と,その画像がどの組織かを示すラベル(label)のペアで構成されていることがわかります.

データの前処理

ここでは,画像・テキストデータの前処理と,分類タスクのラベルを指定します.

from torchvision.transforms import Compose, Resize, ToTensor, Normalize, InterpolationMode

TISSUE_CLASSES = [
    "adipose",
    "background",
    "debris",
    "lymphocytes",
    "mucus",
    "smooth muscle",
    "normal colon mucosa",
    "cancer-associated stroma",
    "colorectal adenocarcinoma epithelium"
]

size = processor.image_processor.size["height"]
mean = processor.image_processor.image_mean
std = processor.image_processor.image_std

_transform = Compose([
    Resize((size, size), interpolation=InterpolationMode.BILINEAR),
    ToTensor(),
    Normalize(mean=mean, std=std),
])


def preprocess(examples):
    pixel_values = [_transform(image.convert("RGB")) for image in examples["image"]]
    captions = [TISSUE_CLASSES[label] for label in examples["label"]]
    inputs = processor.tokenizer(
        captions,
        max_length=64,
        padding="max_length",
        truncation=True,
        return_attention_mask=True,
    )
    inputs["pixel_values"] = pixel_values
    return inputs


data = data.map(preprocess, batched=True, remove_columns=["image", "label"])

ファインチューニングの準備

データコレクターの設定をします.

import torch


def collate_fn(examples):
    pixel_values = torch.tensor([example["pixel_values"] for example in examples])
    input_ids = torch.tensor([example["input_ids"] for example in examples])
    attention_mask = torch.tensor([example["attention_mask"] for example in examples])
    return {
        "pixel_values": pixel_values,
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "return_loss": True,
    }

まずは,学習のハイパーパラメータをTrainingArgumentsで設定します.
学習の量の設定などをnum_train_epochsを自由に設定してください.また,GPUのメモリ量からper_device_train_batch_sizeを細かく修正してください.

from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="medsiglip-448-ft-crc100k", # モデルの保存先
    num_train_epochs=2,                   # 学習エポック数
    per_device_train_batch_size=8,        # バッチサイズ(学習)
    per_device_eval_batch_size=8,         # バッチサイズ(検証)
    gradient_accumulation_steps=8,
    logging_steps=50,                     # ログ表示の頻度
    save_strategy="epoch",                # エポックごとにモデルを保存
    eval_strategy="steps",
    eval_steps=50,                        # 検証の頻度
    learning_rate=1e-4,                   # 学習率
    weight_decay=0.01,
    warmup_steps=5,
    lr_scheduler_type="cosine",
    push_to_hub=True,                     # Hugging Face Hubにプッシュ
    report_to="tensorboard",              # wandbでも保存可能
)
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=data["train"],
    eval_dataset=data["validation"].shuffle().select(range(200)),  # Use subset of validation set for faster run
    data_collator=collate_fn,
)

学習

学習には数十分から数時間かかります.

trainer.train()

学習が完了したら,モデルを保存します.

trainer.save_model()

ファインチューニングしたモデルの評価

テストデータのロードや,ベースラインの評価の部分は省略します.
ファインチューニングしたモデルの評価を以下で行います.

ft_model = AutoModel.from_pretrained(training_args.output_dir, device_map="auto")

ft_predictions = []
for batch in test_batches:
    images = [Image.open(io.BytesIO(image["bytes"])) for image in batch["image"]]
    inputs = processor(text=TISSUE_CLASSES, images=images, padding="max_length", return_tensors="pt").to("cuda")

    with torch.no_grad():
        outputs = ft_model(**inputs)

    logits_per_image = outputs.logits_per_image
    ft_predictions.extend(logits_per_image.argmax(axis=1).tolist())

ft_metrics = compute_metrics(ft_predictions)
print(f"Fine-tuned metrics: {ft_metrics}")
Fine-tuned metrics: {'accuracy': 0.955, 'f1': 0.9537557625744282}

実験内容

ここからが本記事のメインとなる,内視鏡画像データセットでの実験です.

使用したデータセット

今回の実験では,内視鏡検査の画像データセットとしてHyperKvasir[2]を使用しました.
このデータセットは,消化器の内視鏡検査で撮影された画像が含まれており,約11万枚の画像とそれに対応する解剖学的なランドマークや病理所見のラベルから構成されています.ラベルが付与されている約1万ペアのうち,論文で使用された5 foldを参考に,3つをtrainとし,1つをvalidation,1つをtestとして利用した.外科手術領域や内視鏡領域のデータセットはライセンスが厳しいものが多い中,HyperKvasirはCC BY 4.0ライセンスで提供されており,研究用途以外でも使用しやすいライセンスです.

学習データの前処理として,まず元データセットのラベルに対していくつかの加工を行いました.
まず,HyperKvasirの論文中のラベルで,自然な表現となるようにハイフン(-)を空白( )に変換し,画像も448にresizeしました.
また,日本語モデルの学習にあたっては,これらの英語ラベルを機械翻訳して日本語のキャプションとして使用しました.

結果

英語モデルと日本語モデル,それぞれ1 epochのファインチューニングを行った結果,以下のようになりました.評価指標にはAccuracyを使用しております.

言語 zero shot ファインチューニング
英語 12.4% 82.4%
日本語 6.8% 81.4%

結果から読み取れるポイント

  1. ドメイン外データに対する低いzero shot性能:
    ファインチューニング前の性能(zero shot)は,英語で12.4%,日本語で6.8%と実用レベルには達していません.これはMedSigLIPの事前学習データに内視鏡画像が含まれていないため,未知のドメインに対しては汎化性能が限定的であることが示唆されます.特に日本語は英語と比較して低リソース言語であるため,さらに低い結果となりました.

  2. ファインチューニングの効果:
    わずか1エポックのファインチューニングで,精度は英語・日本語ともに80%を超えました.特に日本語モデルは向上幅が**+74.6%**であり,MedSigLIPが新しいドメインと言語に対して高い学習能力と適応性を持つことがわかります.

実際のコードは以下で試すことができます.

GitHubにも同様のコードを公開しております.

https://github.com/yuki-tashiro/medgemma-demo

制限事項

今回の検証にはいくつかの限界があり,結果を解釈する上で考慮が必要です.

  • 機械翻訳によるラベルの品質:
    日本語ラベルは機械翻訳によって作成したため,一部に不自然な表現や専門用語の誤りが含まれています.ラベルの品質がモデルの学習に与えた影響は無視できず,より厳密な評価を行うためには,医療専門家による翻訳やアノテーションが望ましいと考えられます.

  • 限定的な学習設定:
    今回は「手軽に試す」というコンセプトのため,ファインチューニングは1 epochのみとしています.データセットの論文ではResNet-152に対して50 epochの学習を行っております.追加の学習を行うことで,さらに性能が向上する余地が考えられます.

最後に

本記事では,MedSigLIPの公式Notebookを用いて,その使い方から実際の学習例までを紹介しました.

オープンソースの高性能モデルは海外製が主流であり,そのままでは日本の医療データに対して十分な性能を発揮できないケースもあります.しかし本記事で示したように,ファインチューニングによってその性能を特定のタスクに最適化し,精度向上させることが可能です(一方で,特定のタスクに特化するほど,元のモデルが持っていた汎用的な性能はトレードオフになる点には注意が必要です.).

このNotebookをベースに,データセットを変えたり,学習設定を調整したりすることで,さらに様々な応用が考えられます.本記事が,医療分野におけるAI活用の可能性を広げる一助となれば幸いです.

参考文献

脚注
  1. Intelligent Internet. (2025). II-Medical-8B: Medical Reasoning Model. ↩︎

  2. Borgli, H., Thambawita, V., Smedsrud, P. H., Hicks, S., Jha, D., Eskeland, S. L., ... & de Lange, T. (2020). HyperKvasir, a comprehensive multi-class image and video dataset for gastrointestinal endoscopy. Scientific data, 7(1), 283. ↩︎

Discussion