【Amazon Titan】画伯的な絵を描かせたとき、画像生成AI自身は「何を描いたの？」が分かるかを調べる

## 前置き：この記事で扱う生成AI

2023年の11月28日に、Titan Image Generatorが使えるようになりました。
StableDifusionやDALL-Eと同じ画像生成AIです。

https://aws.amazon.com/jp/about-aws/whats-new/2023/11/amazon-titan-image-generator-model-bedrock-preview/

同じ時期に、Titan Multimodal Embeddingsも使えるようになりました。
画像とテキストをベクトル表現（埋め込みベクトル）に変換できる生成AIです。
※データがどのくらい似ているのかを比べることができるのがベクトル表現です。

https://dev.classmethod.jp/articles/new_bedrock_taitan_multimodel_embeddings_g1_ga/

この記事では、この2つのモデルを扱います。

## この記事で調べること

人間に理解できない画像を生成させて、生成AI自身に「これは何を描いたの？」と聞いたら、描かれたものを認識することができるのでしょうか？

![doc-image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/f3ba9dc3-8d70-80c8-01db-2c61fe66ec8d.png)

画像生成AI自身は言葉を返せないので、画像を生成するAIと、画像について質問をするAIは別のものにする必要があります。たとえば絵を描くAIにはStable Diffusionがあります。絵を識別する能力が高いAIには、Claude3やGPT4Vがあります。

ただ、これらは全く違う会社が全く違う時期に出したAIですから、人間で言えば別人です。
「これは何の絵だと思う？」と別人に聞いても、人間と同じ回答しか返ってこないはずです。

![doc-image-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/f46b2929-5f7e-e36f-45d3-edb7c08e97e2.png)

一方で、Titanの埋め込みと画像生成は、同じ会社が同じ時期に同じシリーズとして発表しています。ほぼ同一人物、そうでなくても近いデータで学習していて、かなり似たAIのはずです。

![doc-image-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/bc51c7c0-ff6a-0767-ea1e-4eaca3a506c4.png)

もしかすれば人間に区別のつかないものでも、描いた本人なら区別がつくかもしれません。
この記事では、それを検証します。

## 検証の環境

Amazon Titanを使います。リージョンはバージニアです。
- Amazon Titan Multimodal Embeddings G1（boto3から実行）
- Amazon Titan Image Generator（コンソールから実行）
- Python 3.10

## 検証に使ったソース

ソースコードはこちらのレポジトリに置いています  
READMEの手順に沿って再実施することができます

2024/03/30 追記：ソースを整形しました

https://github.com/ShotaOki/vector-image-compare/tree/master


## 検証手順の概要

**手順：検証は以下の手順で実施します**

1. 犬、猫の品種名を15件ずつ用意して、それぞれテキストからベクトル表現に変換します
2. ベクトル表現（1024次元）から、犬と猫の分類にかかわりの薄い次元を除去します
3. 除去して145次元になったベクトル表現を、PCAで2次元まで次元削減します
4. 2次元になったベクトル表現をグラフ化します（赤が犬、青が猫です）  
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/a3a2f8f0-06f4-7247-b1b1-0f4728996f53.png" width="400"></img>
5. グラフの中に、AI生成画像のベクトル表現（オレンジ色の点）をプロットします  
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/90ecdbc3-c069-22f6-ec7b-3ea277c16ae5.png" width="400"></img>

**観点：確認する観点は以下の2点です**

- AI生成画像は、元プロンプトと比べて、どの程度ベクトル表現が変化するのか
- 元プロンプトに犬猫を含んでいて、生成画像から犬猫の区別がつかないとき、犬猫の分類は正しくできるか

**結果と結論：結論**

- AI生成画像と元プロンプトは、ベクトル表現の位置が異なる
- 元プロンプトに犬猫の表現があったとしても、生成画像が曖昧なら判定できない

以下、実際の検証です。

## 埋め込みベクトルを可視化する

比較をするために、まずは埋め込みベクトル(1024次元)を可視化する必要があります。

犬15種類、猫15種類の品種名を用意しました。

<details><summary>用意したデータ</summary>

```text: 犬の品種名
Pomeranian
Schnauzer
Siberian Husky
Yorkshire Terrier
Shih Tzu
Poodle
Golden Retriever
Labrador Retriever
Maltese
Pekingese
Chihuahua
Dachshund
Dolmatian
Welsh Corgi
Bulldog
```

```text:猫の品種名
Abyssinian
Siberian
Siamese
Scottish Fold
Norwegian Forest Cat
Himalayan
Bengal
Munchkin
Maine Coon
Ragdoll
Russian Blue
Persian
Japanese Bobtail
Selkirk Rex
American Shorthair
```

</details>

犬と猫の品種名30件と、「Dog」、「Cat」の単語をベクトル化します。

```python: ベクトル化
def create_embeddings(input_text: str) -> Embeddings:
    """
    テキストをベクトル化する
    """
    model_id = "amazon.titan-embed-image-v1"
    output_embedding_length = 1024

    # Create request body.
    # 画像の時は、inputTextの代わりにinputImageを指定して、base64形式のデータを入れます
    body = json.dumps(
        {
            "inputText": input_text,
            "embeddingConfig": {"outputEmbeddingLength": output_embedding_length},
        }
    )

    bedrock = boto3.client(service_name="bedrock-runtime", region_name="us-east-1")

    accept = "application/json"
    content_type = "application/json"

    response = bedrock.invoke_model(
        body=body, modelId=model_id, accept=accept, contentType=content_type
    )

    response_body = json.loads(response.get("body").read())
    return EmbeddingsText(
        embedding=response_body.get("embedding"), input_text=input_text
    )
```

### ベクトル表現を可視化する

目に見える形で比較するために、まずベクトル表現を可視化する必要があります。
ベクトル表現は1024次元のベクトルですから、そのまま可視化するわけにはいきません。

#### まずはそのまま可視化する

未加工の状態で、ベクトル表現を波形として表示してみます。
赤が犬、青が猫ですが、さすがに情報量が多すぎて読みようがありません。

![raw_wave.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/8c9178a4-3406-0c78-cb0e-7c3d1366cf2f.png)

PCA（主成分分析）で次元削減をします。

```python: PCAで次元を減らす
# 必要なライブラリをインポートする
from sklearn.decomposition import PCA
# PCAを取得する
pca = PCA(n_components=n_components)
# n_componentsの次元まで次元を減らして、結果を返す
result = pca.fit_transform(
    [e.value.embedding for e in data_list.entity_list]
)
```

たとえばn_componentsを10にすると、1024次元あったベクトルを10次元まで減らしてくれます。

もちろん次元を減らしすぎると破綻します。コサイン類似度で犬の品種15件、猫の品種15件を答えさせた時に、30件中どれだけ誤答したかを調べて、破綻するしきい値を見つけます。

削減後の次元数を1～31で設定して、誤答数の変化を表にしました。

|次元数|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|$\textsf{PCAの結果}$|$\textsf{15}$|9|9|8|4|4|2|2|2|2|0|0|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|

グラフにすると以下のようになります。
縦軸が誤答数、横軸が削減後の次元数です。

![dim_reduce.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/57abca70-9506-1e47-edad-b9ba683e8aa8.png)

十分に次元が大きいと全て正答し、次元数を削るほど誤答が増えます。
2次元では30%ほどが誤答です。現時点のものを2Dグラフで可視化すると以下のようになります。「Dog」と「Cat」の基準になるデータの距離は近く、猫の品種名を表す青の点と、犬の品種名を表す赤の点が入り混じっています。

![2classification.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/044c6e8e-1774-eae2-b52d-775df97b1256.png)

16次元を可視化すると以下のように見えます。

![16_wave.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/e16bbf82-2948-f15d-70ae-70642dc83516.png)

前衛絵画みたいですね。
さすがにもう少し削らないとデータが読めません。

#### データをマスクする

犬と猫であまり差のない特徴があります。たとえば四本足で歩いているか、体毛があるかです。また、ペットショップでの値段だったりとか、飼いやすさだったりとか、生息地の分布だったりとか、そういった情報は、「ヨークシャーテリア」や「アビシニアン」のように品種としては持っていても、「犬」や「猫」の大きな区分にはない情報です。

そういった情報をベクトルから削り取ります。

いま、データはこのようになっています。横方向に1024次元の数値が並んでいます。

![matrix.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/f142cb0d-acdd-74a6-c870-0a56c6324338.png)

これを、縦方向に1次元目のデータだけを取って、縦にクラスタリングします。
下図の赤色の四角の枠の方向に配列を作って、DogとCatのどちらに近いのかを調べます。

![matrix - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/7477dde7-b089-4714-202b-d2cdb9a8f38e.png)

その結果をグラフにしたものが以下のようになります。
縦軸は分類の誤答数、横軸は次元です。1024次元全てで誤答率を見ています。

![histgram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/0dd3031d-3899-6e08-0cac-382a01fd5d08.png)

そうすると、グラフに明確な水平線が出ていることが分かります。
この水平線は、「全て犬、または全て猫で分類結果が返ってきた場所」です。

基準データ（DogとCatをベクトル化したもの）をグラフにすると、このようになります。
グラフの線が青に近ければ猫、グラフの線が赤に近ければ犬です。

![dog-and-cat-single-vector.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/f0fc7a4f-17ed-58e3-9fb8-cfb312808d1e.png)

それぞれの品種名を調べて、青と赤のどちらに近いかを比べるのですが、全ての品種名の線が下図のオレンジ色のあたりを通る場所も少なくありません。犬や猫の分類には深く関わらない情報が入っている場所です。

![dog-and-cat-single-vector.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/70f6669c-ab07-361e-924b-fdf7b138d5d6.png)

こういった情報を除外して、分類に影響のあるデータだけを残します。

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/db5803b5-a5fa-3b32-0c51-83790d7ec590.png)

犬と猫の判別にかかわりのない情報を除外すると、145次元のベクトルになりました。
この状態で再度1～31次元までPCAで次元削減して、先ほどと同じ手順で、データが壊れない限界を調べます。

|次元数|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|$\textsf{PCAの結果}$|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|

グラフにすると、以下のようになります。

![dim-reduce-crop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/9337c306-54c7-ef78-660f-ac3898a0bec5.png)

3次元までクラスタリング結果を保ったまま削減できました。2次元でも誤答は1つですから、十分に可視化できそうです。

2次元の散布図に表示すると、下のようになりました。

![dog-cat-map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/c3d97e15-a071-e3e8-effd-246e7ddc868c.png)

犬、猫のそれぞれのベクトル表現が可視化されて、はっきりとグラフ上で分かれていることが確認できます。

## 画像を分析する

では、インターネット上で拾った犬と猫の画像をベクトル化して、グラフ上のどこに点が表示されるのかを確認します。
画像のベクトル表現はオレンジ色で表示するように、matplotlibを設定しました。

|対象画像|グラフ|
|---|---|
|![dog-picture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/e2e0e154-7e2d-a01d-64dc-2dc60eec62da.jpeg)|![image_dog_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/19d3ff28-1590-1485-7c74-160643171a0e.png)|

> 出典：パブリックドメインQ「雪とラブラドールレトリバー」
https://publicdomainq.net/labrador-retriever-dog-photo-0076496/

左下の端に出力されました。明らかに犬です。
最も近い点も「Labrador Retriver」になっています。品種も正答です。

|対象画像|グラフ|
|---|---|
|![cat-picture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/567650ca-38c6-0ea0-57bd-9d631b51875e.jpeg)|![cat-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/f178d662-c9f5-5dd3-be15-c932b04c1d17.png)|

> 出典：パブリックドメインQ「まどろむ猫」
https://publicdomainq.net/cat-sleeping-animal-photo-0078549/

右端に出力されました。明らかに猫です。
最も近い点は「American Shorthair」のようです。たしかにアメショに見えます。

|対象画像|グラフ|
|---|---|
|![nekomimi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/b2f77dec-4b1b-1ffd-36be-4595b7fcbd89.jpeg)|![nekomimi-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/25869ba6-c2c4-5d1e-f264-899bd3424c2b.png)|

> 出典：パブリックドメインQ「夜の古い街並みと猫耳の少女」
https://publicdomainq.net/girl-cat-ear-0071480/

おそらく「猫耳」のプロンプトで出力されたAI画像です。
やや犬と猫の境界に近い場所ですが、猫だと正しく分類されています。
最も近い点は「Siberian」のようです。サイベリアンの画像は以下の通りです。

![saiberian.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/5055651e-c8d9-94a1-d4a9-0c0df51104e6.jpeg)

> 出典：みんなの子猫ブリーダー「サイベリアンの人気毛色は？」
https://www.koneko-breeder.com/magazine/11604

拾った画像を検証して、正しく分類できること、画像のベクトル座標が正しくグラフ上で可視化できることが確認できました。

## Titan画像生成AIの出力画像を分類する

ここからが本題です。

Titanを使って、コーギーの画像を作成しました。
> プロンプト：Welsh Corgi
プロンプト強度：10

![generate-cogi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/0a6f3d64-771e-a5fb-9b62-b384bb018800.png)

この画像をベクトル化して分析すると、以下のようになりました。

|対象画像|グラフ|
|---|---|
|![welsh-cogi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/a04025b9-ffce-f529-15d6-96ef01df79d9.png)|![cogi-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/157c1e79-9091-dab1-0d68-a86882efe620.png)|

同様に、アメリカンショートヘアでもやってみます。
> プロンプト：American Shorthair
プロンプト強度：10

![amesho.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/fc92bc80-0db7-8c3d-73d9-875060a482a7.png)

|対象画像|グラフ|
|---|---|
|![amesho.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/d5b669ad-d776-01b1-c316-68cf47f27224.png)|![amesho-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/c87e16e9-5973-2c18-6c95-1ad90323d177.png)|

犬と猫の分類は正しく動いています。
ですが、テキストと生成後の画像で、ベクトルの座標が少しずれていることが分かります。

コーギーはどちらかと言えばブルドッグに近い場所に、アメリカンショートヘアもサイベリアンに近い場所にいます。

今度は猫耳の画像を生成して検証してみます。
> プロンプト：Girl with cat eyars on her head

![nekomimi-titan.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/a1e74bc5-14a8-929f-79b0-1ad480050b92.png)

|対象画像|グラフ|
|---|---|
|![catear.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/07846b29-d4a8-3092-8436-418bfb4a76f8.png)|![catear-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/7dcb9a9d-03de-2392-ab8a-395b0d47a36c.png)|

猫だと判定されています。

今度は犬耳の画像を生成します。
> プロンプト：Girl with dog eyars on her head

![dogeyar-titan.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/b0764843-4d05-dcca-8207-b285e12f6724.png)

|対象画像|グラフ|
|---|---|
|![dogear.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/01c1a15b-e09b-aa7e-5c4e-7945b1b09367.png)|![dogear-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/0b92350d-9a0d-d88d-6acc-f900227c7bc0.png)|

人間が見ても「犬…？」と思う画像ですが、AIも同じように猫だと判定しています。

# 結論とまとめ

人間に理解できない絵を出力して、「何を描いたの？」と聞いても、描いたAI自身も答えられないようです。

このことから、以下のことが言えると思います。

- 画像生成AIでプロンプトから画像に変換すると、そのデータに対応するベクトル表現は変わる
- dogのプロンプトで出力した画像が、dogのベクトル表現に分類できるわけではない
- 画像を分析しても、プロンプトのリバースエンジニアリングはできない