見出し画像

画像生成AIがやっていることのイメージをざっくり体感するため、簡単な実験をやってみた

こんにちは。すうちです。

今回は、画像生成AIの仕組みを単純化した方法で体感した話です。

--



はじめに

今こちらの本を読んでいます。

O'Reilly Japan - ゼロから作るDeep Learning ❺ (oreilly.co.jp)
斎藤 康毅 著

一度ひととおり読みましたが、理解度はまだ半分程度です…汗

生成AIの進化はすさまじく、画像に限らず色んなツールやサービスも提供されていますが、個人的には「こんなスゴイことをどうやって実現するのか?」の方に以前から興味はありました。

それで、たまたま本屋で見つけて読んでみました。内容は初心者向けに丁寧な説明で読みやすく、正規分布の生成の話から画像生成の肝である拡散モデルまで辿る流れでした。

ただ、細かい理解は数学や統計的な知識もやはり必要で、冒頭のとおりボンヤリした理解にとどまっています…笑

そんな中、通勤中に窓から風景を眺めていて「このやり方なら生成AIの概念を簡単に試せるのではないか??」とふと思いついたことがありました。

以降、その試行結果です。難しい数式は登場せず、生成AIでやっていることのイメージがふんわり分かって頂けるかもしれません。

※実際に生成AIで実現されている方法とは異なります…


そもそも画像とは

画像データは、幅と高さ、そしてチャネル数(通常RGBの3チャネル)で構成されています。※RGBは、Red、Green、Blueの三原色です。

各画素(ピクセル)は、8ビットの値を持っています。これにより、各色の強さを0~255段階で表現できます。例:R=255、G=0、B=0の場合、その画素は赤になります

画像データのイメージ

また画像の解像度が上がると、幅と高さの画素数が増えるのでデータ量も大きくなります。

最近の画像生成ツールは、4K相当(3840x2160ピクセル)も生成できるそうですが、画素数が増えると難易度が上がる(計算コストも高くなる)ので、有料プランがあったり無料プランは生成枚数を制限していると想像します。


今回試した前提条件とモデル

実際の生成AIで使われているモデルは論文の理論を正しく実装したり、大量の学習データをもとに生成できる仕組みがあると思います。

ただ、今回試したいのは生成AIがやってることをざっくり体感することが目的です(しかも、できるだけ簡単に…)。

そこで、以下の前提条件を考えました。

・画像は3x5の0~9の数字(1チャンネル)
・画素(3x5=15個)はランダム生成
・画素表現は10段階
・目的の数字と画像はベクトル化して比較
・ベクトル類似度が近い画像を表示

今回試した前提条件と実験方法

一般的な画像生成AIができることに比べると、かなり条件しぼって簡単にしていますが、この程度ならできるのでは?と思われたでしょうか…汗


まずは画像生成

今回、生成したい理想の画像(幅・高さ=3x5)です。

理想の画像(数字0〜9)

下記は1の画像を生成した例ですが、3x5のマスに色の濃淡が見えると思います。

例:数字1の画像(拡大)

前述のとおり、各画素(1マス)の値は10段階でランダム生成したものです。これらの濃淡が上手く組み合わさると数字に見えるはずが目論見です。

ちなみに、最初は画像生成の数が足りず、以下のような感じでした。

どの数字もよくわからない.…後述のベクトル類似度は40~70%程度


目的の数字をベクトル化

画像を生成後、ベクトル類似度で目的の数字を表示(選択)するために数字のベクトル情報が必要です。

今回は理想画像の特徴をベクトル化(黒白の位置を0/1表現)しました。

生成したい画像の理想の黒白位置(数字0)


これらのベクトルを0~9の数字ごとに持ち、生成画像と目的の数字の類似度をみています。

# 0
  1 1 1 1 0 1 1 0 1 1 0 1 1 1 1 

# 1
  1 1 0 0 1 0 0 1 0 0 1 0 0 1 0# 9
  1 1 1 1 0 1 1 1 1 0 0 1 0 0 1

ちなみに、一般の画像生成AIも入力テキストをベクトル化する点は同じです。その情報を元に画像を生成しています(生成AIモデルは、CLIPというテキストをベクトル変換するAIを使う例が多いそうです)。


生成画像とベクトル類似度を比較

ベクトル類似度の計算は、以前noteに書いた方法と同じです。生成した画像がどれだけ理想の画像に近いかを判定しました。


数字2のベクトルに近い生成画像の例

余談として、少し細かい話で画素の値は暗いほど0に近く、明るいほど255(正規化すると1)に近くなります。

なので、ベクトル類似度を計算する時は、さきほどの数字ベクトルは0/1を反転した値にしています(最初からそう作るべきでしたが…)。


実際に試してみた結果…

以下、前述の条件で生成した結果です。

上から下に行くほど、目的の数字(理想値)と類似度が高くなっています。

画像生成数 100,000からピックアップ
(画素表現は10段階)

ベストの類似度は数字によって異なりますが、81~96%程度でした。上の方は認識しずらいものもありますが、一番下は数字として認識できると思います。。。

試しに画素の表現を2段階にした条件も確認しました。

画像生成数 100,000からピックアップ
(画素表現は2段階)

こちらは画素の発生頻度のばらつきが減るので、もっとはっきりした数字として認識しやすいと思います(一番下の類似度は全て100%でした…)。


以下、Python プログラム(参考)です。

数字ベクトル

def get_num_vec(num_idx, is_inverse=True):

    NumList = [
        [ # 0
             1,1,1,1,0,1,1,0,1,1,0,1,1,1,1,
        ],
            
        [ # 1
             1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,
        ],
            
        [ # 2
             1,1,1,0,0,1,1,1,1,1,0,0,1,1,1,
        ],
            
        [ # 3
             1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,
        ],
            
        [ # 4
             1,0,1,1,0,1,1,1,1,0,0,1,0,0,1,
        ],
            
        [ # 5
             1,1,1,1,0,0,1,1,1,0,0,1,1,1,1,
        ],
            
        [ # 6
             1,1,1,1,0,0,1,1,1,1,0,1,1,1,1,
        ],
            
        [ # 7
             1,1,1,1,0,1,0,0,1,0,0,1,0,0,1,
        ],
            
        [ # 8
             1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,
        ],
            
        [ # 9
             1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,
        ],
    
    ]
    
    if is_inverse:
        inv_list = []
        for v in NumList[num_idx]:
            v = 0 if v == 1 else 1
            inv_list.append(v)
            
        return inv_list
    
    return NumList[num_idx]


画像生成と表示

import numpy as np
import random
import matplotlib.pyplot as plt
from tqdm import tqdm

from number_vec import get_num_vec


# コサイン類似度
def calc_cossim(vec0, vec1):
    return np.dot(vec0, vec1) / (np.linalg.norm(vec0) * np.linalg.norm(vec1))

# Plot関数
def draw_img(title, img):
    plt.title(title)
    plt.gray()
    plt.axis('off')
    plt.imshow(img)
    plt.show()


# データ生成(ランダムN個)
seed_key = random.randint(100, 200)
seed_key = 167 # Good
random.seed(seed_key)
print(f'seed={seed_key}')

num = 500*100*2

row, col = 5,3
min_val, max_val = 0, 9

data_list = [] 
for loop in tqdm(range(num)):
    d_ary = []
    for n in range(row*col):
        val = random.randint(min_val, max_val)/max_val
        d_ary.append(val)
    np_data = np.reshape(d_ary, (row, col))
    data_list.append(np_data)

# Plot条件
map_row, map_col = 5, 10
map_list = []
th = 0.90

for create_num in range(0, 10):
    
    candidate_list = []
    best_idx   = 0
    best_score = 0

    num_idx = create_num
    num_vec = get_num_vec(num_idx, is_inverse=True)
    target  = np.array(num_vec)
    t_d = target.flatten()
    
    print(f'\n ##### Searching .. {create_num}')
    for i, np_data in enumerate(data_list):
        v = np_data.flatten()
        # 生成画像の類似度判定
        res = calc_cossim(t_d, v)
        if res > best_score:
            best_score = res
            best_idx = i
            is_th = (res > th)
            candidate_list.insert(0,[i, res, is_th])

    better_idx = [i for (i, res, is_th) in candidate_list if is_th == True]
    target_num = len(better_idx)
    if target_num > 0:
        print(f'\n {target_num} found !!')
    else:
        print(f'\n Not Found over {th*100:.1f} % but, best match rate is..{best_score*100:.1f} %')
    
    # 類似度高い生成画像の取り出し
    sample_imgs = sorted(candidate_list[1:map_row])    
    for (i, res, is_th) in sample_imgs:
        
        if i == best_idx:
            continue
        
        np_data = data_list[i]
        title = f' Num = {create_num}, seed={seed_key} \n match: {res*100:.1f} %'
        draw_img(title, np_data)
        map_list.append(np_data)
    
    np_data = data_list[best_idx]
    title = f' Num = {create_num}, seed={seed_key} \n Best match: {best_score*100:.1f} %'
    draw_img(title, np_data)
    map_list.append(np_data)

# 類似度上位5つの生成画像の表示
fig = plt.figure(figsize=(20,10), dpi=100)

idx = 0
for c in range(map_col):
    for r in range(map_row):    
        
        img = map_list[idx]
        pos = r*map_col + c+ 1
        
        ax = plt.subplot(map_row, map_col, pos)
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)
        plt.axis('off')
        plt.imshow(img)
        idx += 1

plt.show()


最後に

単純化した条件ではありますが、思いついた案を実際に試して確認できたので、個人的な満足度は高かったです。

実際の生成AIは、大量の学習データとさらに複雑な理論やモデルを駆使して、いろんな条件に対応した画像生成を実現していると思います。

その肝である拡散モデルは、画像にノイズを追加したり、除去することを学習するとありましたが、個人的になぜそれで多様な画像が作れるのか?が(数式など理解できてない所もあるためか…)まだ自分の中で消化できてない状況です。まあ、使う分には全然知らなくても困らないですけどね。


余談として、じつはさらに続けてス〇イム生成にも今回挑戦したのですが、、、

理想と現実の違い…

ス〇イムを表現するには最低13x13(=169個)の画素が必要で、数字(15個)の10倍以上になるので難易度高いのは間違いないのですが、ランダム生成数やSeedを変えたり、理想ベクトルに近くなる小細工も試した結果、どうやらこの辺が限界のようです。。。

心優しい人にはス〇イムに見えなくもないかもしれませんが、、、今回はこれで締めたいと思います…笑

最後まで読んで頂き、ありがとうございました。

いいなと思ったら応援しよう!

この記事が参加している募集