マルチモーダル AI モデルを試してみた

# はじめに

以前に `Gemma` を使って文章生成と、`PaliGemma` を使って画像認識の生成 AI プログラミングを試してみました。

[生成 AI プログラムを試してみた #LLM - Qiita](https://qiita.com/tinymouse/items/ed64a452bcddf4b8af7d)
[画像認識 AI モデルを試してみた #LLM - Qiita](https://qiita.com/tinymouse/items/a34960e8942ba2827c58)

`Gemma 3` が公開されて、マルチモーダルに対応したと聞いて、試してみることにしました。

参考：[マルチモーダルAI（Multimodal AI）とは？：AI・機械学習の用語辞典 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2207/04/news016.html)

# Gemma 3 を試してみる

`Gemma 3` を使った AI プログラムを試してみます。

以下の記事を参考にしました。

* [次世代AI「Gemma3」徹底解説｜りんか](https://note.com/gabc/n/nc28115b82214)
* [「Gemma3」を試す](https://zenn.dev/kun432/scraps/f017bdabb88bb9)

## 実行環境を用意する

以前と同様に Google Colab を使ってみます。別の環境でも構いません。

参考：[生成 AI プログラムを試してみた #LLM - Qiita](https://qiita.com/tinymouse/items/ed64a452bcddf4b8af7d#google-colab-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)

Google Colab  のランタイムは「T4」を使いました。

以下のコード例は、Google Colab あるいは Jupyter Notebook で実行するものです。

## ライブラリをインストールする

`Transformers` ライブラリを使うのでインストールします。

```
! pip install -U transformers
```

## モデルを用意する

モデルはリポジトリサービスから入手します。以前と同様に `Hugging Face` を使用します。

参考：[生成 AI プログラムを試してみた #LLM - Qiita](https://qiita.com/tinymouse/items/ed64a452bcddf4b8af7d#%E3%83%A2%E3%83%87%E3%83%AB%E3%82%92%E7%94%A8%E6%84%8F%E3%81%99%E3%82%8B)

## テキスト生成してみる

以前にテキスト生成したときのコードを使ってみます。

### モデルとトークナイザを用意する

```python
import transformers
import torch

# モデルとトークナイザを準備
model = transformers.AutoModelForCausalLM.from_pretrained(
    "google/gemma-3-4b-it",
    device_map="cuda",
    torch_dtype=torch.bfloat16
)
tokenizer = transformers.AutoTokenizer.from_pretrained(
    "google/gemma-3-4b-it"
)
```

### プロンプトを準備して推論を実行する①

モデル名に `it` があるのは指示チューニングされています。プロンプトを指示チューニングしたときのフォーマットにします。

参考：[Gemma のフォーマットとシステムの手順  |  Google AI for Developers](https://ai.google.dev/gemma/docs/core/prompt-structure?hl=ja)

```python
# プロンプトを指定
prompt = '''<start_of_turn>user
ガンダムシリーズで一番おもしろいのは何かな。<end_of_turn>
<start_of_turn>model
'''

# 推論を実行
input = tokenizer.encode(
    prompt,
    return_tensors="pt"
).to(model.device)

outputs = model.generate(
    input,
    do_sample=False,
    max_new_tokens=512
)

output = outputs[0]

# 結果を出力
print(tokenizer.decode(output))
```

結果の例です。↓

```
<bos><start_of_turn>user
ガンダムシリーズで一番おもしろいのは何かな。<end_of_turn>
<start_of_turn>model
ガンダムシリーズで一番面白い作品を選ぶのは本当に難しいですね！人によって好みや評価が大きく分かれるからです。ただ、一般的に人気が高く、おすすめできる作品をいくつかご紹介します。

**1. 機動戦士ガンダム（初代）**
(以下略)
```

### プロンプトを準備して推論を実行する③

チャットモデルテンプレートを使ってプロンプトを準備します。

参考：[HuggingFace Transformers の チャットモデルテンプレート を試す｜npaka](https://note.com/npaka/n/nf5d78c00b3df)

```python
# プロンプトを指定
prompt = [
    {
        "role": "user",
        "content": [
            { "type":"text", "text": "ガンダムシリーズで一番面白いのは何かな。" }
        ]
    },
]

# 推論を実行
inputs = tokenizer.apply_chat_template(
    prompt,
    return_tensors="pt",
    add_generation_prompt=True,
).to(model.device)

(後略)
```

## 画像認識してみる

`Gemma 3` は画像認識できます。

### モデルとプロセッサを用意する

```python
import transformers
import torch

# モデルとプロセッサを準備
model = transformers.AutoModelForCausalLM.from_pretrained(
    "google/gemma-3-4b-it",
    device_map="cuda",
    torch_dtype=torch.bfloat16
)
processor = transformers.AutoProcessor.from_pretrained(
    "google/gemma-3-4b-it"
)
```

### プロンプトを準備する

チャットモデルテンプレートを使ってプロンプトを用意します。`"content"` に `"type": "image"` を指定して画像データを指定します。

```python
# プロンプトを指定
prompt = [
    {
        "role": "user",
        "content": [
            { "type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG" },
            { "type": "text", "text": "何が描かれているか教えて。" }
        ]
    },
]
```

<img width=512 src="https://huggingface.co/datasets/hf-internal-testing/diffusers-images/resolve/main/unidiffuser/unidiffuser_example_image.jpg">

### 推論を実行する

```python
# 推論を実行
inputs = processor.apply_chat_template(
    prompt,
    return_tensors="pt", tokenize=True, return_dict=True,
).to(model.device)

outputs = model.generate(
    **inputs,
    do_sample=False,
    max_new_tokens=512
)

output = outputs[0]

# 結果を出力
print(processor.decode(output))
```

### パイプラインで処理する

上記と同じことをパイプラインで処理してみます。

```python
import transformers
import torch

# パイプラインを準備
pipe = transformers.pipeline(
    "image-text-to-text",
    model="google/gemma-3-4b-it",
    device="cuda",
    torch_dtype=torch.bfloat16
)
```

プロンプトは同じものを使います。

```python
# パイプラインを実行
outputs = pipe(
    text=prompt, 
    max_new_tokens=512
)

output = outputs[0]

# 結果を出力
print(output["generated_text"][-1]["content"])
```

結果の例です。↓

```
この画像には、宇宙服を着た宇宙飛行士が地球の周回軌道上を漂っている様子が描かれています。

*   宇宙飛行士は、地球の地表と宇宙空間を背景に浮かんでいます。
*   地球は青と白の美しい景色で覆われ、雲の模様がはっきりと見えます。
*   宇宙空間には、星々が散らばっていて、宇宙の広大さを感じさせます。

この画像は、宇宙探査の壮大さと、宇宙飛行士の冒険心や畏敬の念を表現していると言えるでしょう。
```

## 実行環境を変更してみる

Google Colab は実行環境の GPU/TPU を選択できます。現在は以下のタイプが選択できました。それぞれスペックを確認しました。

|   |CPU|v2-8 TPU|v5e-1 TPU|T4 GPU|L4 GPU|A100 GPU|
|:---|:--:|:--:|:--:|:--:|:--:|:--:|
|システム RAM|13GB|334GB|47GB|13GB|53GB|84GB|
|GPU RAM|なし|なし|なし|15GB|22GB|40GB|
|ディスク|108GB|225GB|225GB|78GB|78GB|78GB|
|ユニット使用率|約0.07／時|約1.76|約4.5|約1.76|約4.82|約11.77|

前述のプログラムをそれぞれの環境で実行するとどうなるでしょうか。モデルのサイズを変えて確認してみました。

|   |CPU|v2 TPU|T4 GPU|L4 GPU|A100 GPU|
|:---|:--:|:--:|:--:|:--:|:--:|
|gemma-3-4b-it|実行できない|実行できない|○|○|○|
|gemma-3-12b-it|||クラッシュした|メモリ不足|○|
|gemma-3-27b-it|||||メモリ不足|

## レシートを読んでみる

最新の `ChatGPT` や `Gemini` は、レシートの画像を読ませて JSON データにできるようです。`Gemma 3` はどうでしょうか。

<img width=256 src="https://www.mof.go.jp/tax_policy/summary/consumption/09_a.gif">

```python
# プロンプトを準備
prompt = [
    {
        "role": "user",
        "content": [
            { "type": "image", "url": "https://www.mof.go.jp/tax_policy/summary/consumption/09_a.gif" },
            { "type": "text", "text": "何が描かれているか教えて。" }
        ]
    }
]

(後略)
```

結果の例です。↓

```
この画像には、以下が描かれています。

*   **店舗名:** DEFストア 護ヶ関店
*   **日付:** XX年11月1日
*   **レシート:** レシートの内容が記載されています。
*   **商品:**
    *   カップラーメン： 162個 × 3個 = 486円
*   **金額:**
    *   合計：486円
    *   8%対象：486円
    *   内消費税等：36円
    *   お預かり：500円
    *   合計金額：14円
*   **備考:**
    *   ※印は軽減税率対象商品

DEFストア護ヶ関店での11月1日の食事のレシートのようです。
```

JSON にして貰えるでしょうか。

```python
# プロンプトを準備
prompt = [
    {
        "role": "user",
        "content": [
            { "type": "image", "url": "https://www.mof.go.jp/tax_policy/summary/consumption/09_a.gif" },
            { "type": "text", "text": "書かれている内容を JSON にして下さい。" }
        ]
    }
]

(後略)
```

結果の例です。↓

```
{
    "store": "DEFストア 霞ヶ関店",
    "date": "XX年11月1日",
    "receipt": {
        "items": [
            {
                "name": "カップラーメン",
                "quantity": 3,
                "price_per_item": 162,
                "total_price": 486
            }
        ],
        "subtotal": 486,
        "tax": 486,
        "consumption_tax_rate": "8%",
        "service_charge": 36,
        "total": 500,
        "change": 14,
        "notes": "※印は軽減税率対象商品"
    }
}
```