StableDiffusionのレッサーパンダ

StableDiffusionは拡散モデルによってpromptを入力して画像を生成します。
さて、**prompt = "a photo of a lesser panda"** と入力すればレッサーパンダの画像が生成されますが、何でだろうと疑問に思いました。というのもパンダのpromptである**prompt = "a photo of a panda"** が近くにあるではないですか。
こういう場合、finetuningモデルの経験上レッサーパンダを学習しようとすると大量に画像があるパンダの学習が優先されてしまう、という事が起こらないのかと心配になりました。
しかし、想像に反してStableDiffusionはちゃんとレッサーパンダという概念を把握して描くことができるようです。

# 生成画像比較
さて、以下の4種類のpromptをseedを固定して試してみました。
① prompt = "a photo of a panda"
② prompt = "a photo of a lesser panda"
③ prompt = "a photo of a lesser"
④ prompt = "a photo of a panda lesser"

もし、StableDiffusionがレッサーパンダを把握せず、「パンダ$+α$」と捉えているなら③では差分$α$部分、④では順番は違えどほぼレッサーパンダを書き出せるはずです。

結果は
①はパンダの画像
②はレッサーパンダの画像
③は虫の画像
④はパンダの画像（①の画像と近い）
となりました。

以上、③または④でレッサーパンダが書き出せなかった事からStableDiffusionは単純な単語の足し合わせではなく、ちゃんと単語の組み合わせによってパンダとレッサーパンダを別の概念として見分けているのです。

# 見分けているのはCLIPTextEncoderとUnetのどちらか？
レッサーパンダの画像を読み込んでCLIPに["a photo of a panda", "a photo of a lesser panda", "a photo of a panda lesser", "a photo of a cat"]のpromptの内どれが正しいか判別させてみます。


```python
import torch
import clip
from PIL import Image

device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)

image = preprocess(Image.open("CLIP.png")).unsqueeze(0).to(device)
text = clip.tokenize(["a photo of a panda",
                      "a photo of a lesser panda",
                      "a photo of a panda lesser",
                      "a photo of a cat"]).to(device)

with torch.no_grad():
    image_features = model.encode_image(image)
    text_features = model.encode_text(text)
    print(image_features.shape)
    print(text_features.shape)

    logits_per_image, logits_per_text = model(image, text)
    print(logits_per_image.shape)
    print(logits_per_text.shape)

    probs = logits_per_image.softmax(dim=-1).cpu().numpy()

print("Label probs:", probs)

```
結果は以下の様になり、レッサーパンダ90.97%となりました。CLIP時点でレッサーパンダとパンダの区別がちゃんとついていることが分かります。正確にはStableDiffusionではCLIPの最終層はskipされますがおそらくその一個手前でも既に区別はついているのでは、と考えられます。


```python
torch.Size([1, 512])
torch.Size([4, 512])
torch.Size([1, 4])
torch.Size([4, 1])
Label probs: [[3.130e-03 9.097e-01 8.728e-02 4.113e-06]]
```

また、CLIPに福沢諭吉の画像を入れた場合の結果も示します。この場合、「樋口一葉」が91.3%と判定されました。CLIPには福沢諭吉の区別がついていないのは明らかです。

```python
image = preprocess(Image.open("CLIP2.png")).unsqueeze(0).to(device)
text = clip.tokenize(["a photo of a hukuzawa yukiti",
                      "a photo of a shibusawa eiichi", 
                      "a photo of a nastume souseki", 
                      "a photo of a higuchi ichiyou"]).to(device)
...
Label probs: [[0.0798  0.00102 0.00625 0.913  ]]
```

# CLIPTextEncoderの類似度
読み込んだ画像との類似度ではなく、CLIPのText同士の類似度を比較してみる。
やはり、パンダとレッサーパンダの区別はついている。パンダレッサーはレッサーパンダよりパンダの方が近い。

```python
text = clip.tokenize(["a photo of a panda",
                      "a photo of a lesser panda",
                      "a photo of a panda lesser",
                      "a photo of a cat"]).to(device)
with torch.no_grad():
    text_features = model.encode_text(text) 
    text_features = text_features / text_features.norm(dim=-1, keepdim=True)

    similarity = text_features @ text_features.t()
print(similarity)
...
tensor([[1.0000, 0.7422, 0.9092, 0.8237],
        [0.7422, 1.0000, 0.7358, 0.6074],
        [0.9092, 0.7358, 0.9995, 0.7988],
        [0.8237, 0.6074, 0.7988, 1.0000]], device='cuda:0',
       dtype=torch.float16)
```

次に有名な人名を入力してみるとCLIPはやはりちゃんと区別しているのが分かります。
トランプ氏とオバマ氏が比較的近いのはどちらもprompt="a photo of a USA president"の類似度と近いためと思われます。

```python
text = clip.tokenize(["a photo of a Arnold Schwarzenegger",
                      "a photo of a Johnny Depp",
                      "a photo of a Donald Trump", 
                      "a photo of a Barack Obama"]).to(device)
...
tensor([[1.0000, 0.7422, 0.8086, 0.7949],
        [0.7422, 1.0010, 0.7773, 0.7554],
        [0.8086, 0.7773, 1.0010, 0.8721],
        [0.7949, 0.7554, 0.8721, 1.0000]], device='cuda:0',
       dtype=torch.float16)
```


最後に福沢諭吉と福沢ユキジと福モト諭吉と樋口一葉を比べます。
この場合、CLIPにはほとんど区別がついていないように思えました。

```python
text = clip.tokenize(["a photo of a hukuzawa yukiti",
                      "a photo of a hukuzawa yukiji", 
                      "a photo of a hukumoto yukiti", 
                      "a photo of a higuchi ichiyou"]).to(device)
...
tensor([[1.0010, 0.9580, 0.9497, 0.9023],
        [0.9580, 1.0000, 0.9390, 0.9307],
        [0.9497, 0.9390, 1.0010, 0.8794],
        [0.9023, 0.9307, 0.8794, 0.9995]], device='cuda:0',
       dtype=torch.float16)
```



# まとめ
以上の事からパンダとレッサーパンダはきちんとCLIPの時点で判別出来ており、福沢諭吉と樋口一葉をCLIPは判別できないという事が分かりました。StableDiffusionのファインチューニングモデルにおいて一般にCLIPTextEncoderはオリジナルのまま使用し、学習はUnetのみ行うと思われます。
CLIPに含まれない対象をファインチューニングする場合、人名単語が重複するpromptの学習が困難であるというのはCLIPの時点での明確な判別がついてないのが原因ではないか、という推定をすることが出来るかもしれません。
今回使用したのは一般的なOpenAIのCLIPと思われるので、もしかしたらrinnaのjapanese-CLIPとか、Stability AIのCLIPとか、中国のAltCLIPとか他のを使えば「福沢諭吉」と「樋口一葉」を区別することは可能かもしれません。