画像認識 AI モデルを試してみた

# はじめに

以前に、Transformers 、Gemma 、Diffusers などを使って、生成 AI プログラミングを試してみました。

[生成 AI プログラムを試してみた #AI - Qiita](https://qiita.com/tinymouse/items/ed64a452bcddf4b8af7d)

文章の入力に対して文章や画像を生成して出力できました。

これに対して、OpenAI の ChatGPT や Google の Gemini などのサービスでは、画像を入力して文章を出力することができます。

生成 AI プログラムは、モデルを参照して入力内容から出力内容を生成します。モデルは、どんな入力内容に対して、どんな出力内容を生成するか、学習させたものです。

![説明1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/5b5b9df2-4108-c51a-71ee-a61b04648bdd.png)

上記のサービスで利用している AI モデルは、画像の入力と文章の出力を学習させているのですね。

同様のモデルを使えば、自分でも画像から文章を生成するプログラムが書けるはずです。

# 画像認識 AI モデルを試してみる

画像を認識する AI プログラムを試してみます。

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

* [段階的なガイド ローカルでLLaVaを実行する方法](https://anakin.ai/ja-jp/blog/how-to-run-llava-locally/)
* [【PaliGemma】画像を理解できるGoogleのオープン言語モデル | WEEL](https://weel.co.jp/media/tech/paligemma/)

## 実行環境を用意する

以前と同様に 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 あるいは Jupyter Notebook で実行するものです。

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

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

```
! pip install transformers accelerate
```

## モデルを用意する

モデルはリポジトリサービスから入手します。以前と同様に `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)

## LLaVA 、PaliGemma を使ってみる

画像を入力して文章を生成できるモデルに、`LLaVA` があります。画像のエンコーダと LLM の LLaMA を合わせたモデルとのことです。
これを使ってみます。

参考：[画像分析機能を持つオープンソースLLM『LLaVA-1.5』登場 | AIDB](https://ai-data-base.com/archives/56440)

`LLaVA` だけでなく別のモデル `PaliGemma` も使えそうです。Google の PaLI から着想して、画像のエンコーダと LLM の Gemma を合わせたモデルとのことです。

参考：[Googleがオープンソースのビジュアル言語モデル「PaliGemma」を公開 - GIGAZINE](https://gigazine.net/news/20240515-google-paligemma-gemma-2/)

### 画像データを準備する

Hugging Face でチュートリアルのために用意された画像データを使ってみます。

<img width=512 src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/ai2d-demo.jpg">

これを参照して画像オブジェクトにします。↓

```python
import requests
import PIL

# 画像を参照
url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/ai2d-demo.jpg"
image = PIL.Image.open(requests.get(url, stream=True).raw)
```

### モデルとプロセッサで処理する

モデルをダウンロードしてプロセッサを準備します。

```python
import transformers
import torch

# モデルとプロセッサの準備
model = transformers.LlavaForConditionalGeneration.from_pretrained(
    "llava-hf/llava-1.5-7b-hf",
    device_map="auto",
    torch_dtype=torch.float16, low_cpu_mem_usage=True
)
processor = transformers.AutoProcessor.from_pretrained(
    "llava-hf/llava-1.5-7b-hf"
)
```

画像データとプロンプトを入力内容にして推論させて結果を作成させます。

```python
# プロンプトを準備
prompt = """USER: <image>
これは何の画像ですか。
ASSISTANT:
"""

# プロセッサとモデルで推論
inputs = processor(
    prompt,
    image,
    return_tensors="pt"
).to(model.device)

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

作成された結果を出力します。

```python
# 結果を出力
output = outputs[0]

print(processor.decode(
    output,
    skip_special_tokens=True
))
```

結果の例です。↓

```
この画像は、地質学的な地図であり、地層や地下水の流れなどの情報が表示されています。地図には、地下水の流れや地層の層序、地下の洞窟、地下の溶岩などが含まれており、地質学的な知識を持つ人々にとって有益な情報源となっています。
```

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

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

```python
import transformers
import torch

# パイプラインの準備
pipe = transformers.pipeline(
    "image-text-to-text",
    model="llava-hf/llava-1.5-7b-hf",
    device_map="cuda",
    torch_dtype=torch.float16,
    low_cpu_mem_usage=True  # 効かない
)

# プロンプトを準備
prompt = """USER: <image>
これは何の画像ですか。
ASSISTANT:
"""

# パイプラインで推論
outputs = pipe(
    image,
    prompt,
    max_new_tokens=256
)
```

作成された結果を出力します。

```python
# 結果を出力
output = outputs[0]

print(output["generated_text"])
```

結果の例です。↓

```
この画像は、地質学的な地図であり、地層や地下水の流れなどの情報が表示されています。地図には、地下水の流れや地層の層序、地下の洞窟、地下の溶岩などが含まれており、地質学的な知識を持つ人々にとって有益な情報源となっています。
```

### モデルを替えてみる

モデルを `LLaVa` から `PaliGemma` に替えてみます。

```python
# モデルとプロセッサの準備
model = transformers.PaliGemmaForConditionalGeneration.from_pretrained(
    "google/paligemma-3b-mix-224",
    device_map="auto",
    torch_dtype=torch.float16, low_cpu_mem_usage=True
)
processor = transformers.AutoProcessor.from_pretrained(
    "google/paligemma-3b-mix-224"
)
```

あるいは

```python
# パイプラインの準備
pipe = transformers.pipeline(
    "image-to-text",
    model="google/paligemma-3b-mix-224",
    device_map="cuda",
    torch_dtype=torch.float16,
    low_cpu_mem_usage=True  # 効かない
)
```

```python:
prompt = """
これは何の画像ですか。
"""
```

推論させて結果を出力するプログラムは同じです。出力すると↓

```
火山 の さまざま な 部分 の 図 。
```

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

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

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

前述のプログラムをそれぞれの環境で実行するとどうなるでしょうか。

|   |CPU|TPU v2|T4 GPU|L4 GPU|A100 GPU|
|:---|:--:|:--:|:--:|:--:|:--:|
|モデルとプロセッサで処理|終了しない|クラッシュした|○|○|○|
|パイプラインで処理|||○|○|○|

モデル `LLaVa` 、 `PaliGemma` どちらも同じでした。別のサイズの大きいモデルを使うと結果が違うかも知れません。

## UniDiffusers を使ってみる

文章を入力して画像を生成して出力するのに、以前に Stable Diffusion のモデルと Diffusers ライブラリを使いました。
このライブラリに含まれる `UniDiffusers` を使うと、文章から画像を生成するだけでなく、画像から文章を生成できるようです。

[diffusers v0.17.0 の UniDiffuser を使ってみる - ジャコ Lab](https://zako-lab929.hatenablog.com/entry/20240321/1711024821)

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

`Transformers` に加えて、`Diffusers` ライブラリを使うのでインストールします。

```
! pip install diffusers
```

### 画像データを準備する

Hugging Face でチュートリアルのために用意された画像データを使ってみます。

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

これを参照して画像オブジェクトにします。↓

```python
# 画像を参照
url = "https://huggingface.co/datasets/hf-internal-testing/diffusers-images/resolve/main/unidiffuser/unidiffuser_example_image.jpg"
image = diffusers.utils.load_image(url).resize((512, 512))
```

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

パイプラインを準備して処理します。

```python
import diffusers
import torch

# パイプラインの準備
pipe = diffusers.UniDiffuserPipeline.from_pretrained(
    "thu-ml/unidiffuser-v1",
    torch_dtype=torch.float16, low_cpu_mem_usage=True
).to("cuda")

# パイプラインで推論
output = pipe(
    image=image,
    # prompt="この画像は何ですか。",
    num_inference_steps=20, guidance_scale=7.5
)
```

このパイプラインは文章のプロンプトは受付けないようです。↑

```python
# 結果を出力
print(output.text[0])
```

結果の例です。↓

```
An image of an astronaut flying in space over the earth
```