2022.10.11

次世代生成モデル、Diffusion Modelの紹介

こんにちは。次世代システム研究室のA.Z.です。

今回は最近話題になっている、確率モデル+深層ニューラルネットワーク(統合モデル)の一つのdiffusionモデルについて紹介したいと思います。

はじめに

すでに、ご存知の方が多いかもしれませんが、最近話題になっている生成用の機械学習モデル、Stable DiffusionやDall-Eなど、性能が高い生成アルゴリズムやシステムにDiffusionモデルというコンセプトが採用されています。生成モデルとモデルといえば、最初に一番良く知られているのはGAN(Generative Adversarial Network)ですが、その後、他の有名な手法Variational Auto Encoder(VAE)も出てきました。Diffusion modelはVAEと同じく、確率グラフィカルモデル(Probabilistic Graphical Model)と深層ニューラルネットワークの技術を組み合わせをし、hybrid modelの一つです。また、VAEとdiffusion modelは以下の部分が似いていて、diffusion modelはVAEの発展形のモデルとしてよく言われています。
  • 生成プロセスは学習した分布から潜在変数をsamplingし、ニューラルネットワーク上に変化させ、という生成プロセス
  • モデルのloss関数の計算方法はにいています(Variational Inference, Reparameterization Tricksなど)。
 

Diffusion modelについて

Diffusion Modelとは一言いうと、まず少しずつもの壊して、その壊れた状態から復元するというコンセプトです。具体的に、オリジナルデータに対して、各ステップ tに対して、少しずつGaussian Noiseを入れて(Forward process)、T ステップまで行います。Tは大きいければ、最終的にXTは完全にgausian 分布になり、そこから、少しずつnoiseを取り除いて、もとの画像を戻すという学習を行います。

Forward Process:

 

引用:https://lilianweng.github.io/posts/2021-07-11-diffusion-models/



 

Reverse Process:

引用: https://lilianweng.github.io/posts/2021-07-11-diffusion-models/



 

Forward processは基本的にgaussian分布からnoiseを適用し、そして、Reverse processsで、少しずつ加えたnoiseを取り除いてい、各ステップで、元のデータを戻すパラメータを学習する。学習が完了すれば、完全なrandom noiseから、データ生成することができます。

次は各重要な部分をかんたんにに詳しい内容とコードを説明します。
今回は基本的に以下のsampleコードをベースにかんたんにし
https://keras.io/examples/generative/ddim/

元の論文:
DENOISING DIFFUSION IMPLICIT MODELS (J.Song, et.al 2020)
https://arxiv.org/pdf/2010.02502.pdf

いくつか元の論文や実装が異なる部分がありますが、基本的にコンセプトや処理のアイデアは同じで、実装のときの入門としてはわかりやすいと思います。

 

Forward Process

元のデータ(x0)は少しいずつGausian Noiseを加えて、最終的にXTにするのは基本的に以下の式で、定義されます。


tのときにxの値(xt)は基本的に前のt-1の値に依存します。t-1の値から、以下の式でgaussian noiseに加えます。


上記の式は各ステップのβが違う値として、設定必要です。基本的に、各ステップで、βの値が徐々に大きなれば良いですが、普通のdiffusionモデルではβの値はモデルで学習させることが多いですが、参考した実装のコードは以下の関数として定義されます。なので、tの値によって、すぐに計算できて、推論時に、自由にstepのサイスや値を変更できます。

 
def sinusoidal_embedding(x):
    embedding_min_frequency = 1.0
    frequencies = tf.exp(
        tf.linspace(
            tf.math.log(embedding_min_frequency),
            tf.math.log(embedding_max_frequency),
            embedding_dims // 2,
            )
    )
    angular_speeds = 2.0 * math.pi * frequencies
    embeddings = tf.concat(
        [tf.sin(angular_speeds * x), tf.cos(angular_speeds * x)], axis=3
    )
    return embeddings
 
def get_network(image_size, widths, block_depth):
    noisy_images = keras.Input(shape=(image_size, image_size, 3))
    noise_variances = keras.Input(shape=(1, 1, 1))

    e = layers.Lambda(sinusoidal_embedding)(noise_variances)
    .....
そして、Forward processはかんたんに、元のデータに、計算したnoiseに追加するだけです。このおかげで、stepごとのnoiseパラメータが学習が不要になり、全体のアーキテクチャがシンプルになります。
#sample uniform random diffusion times
diffusion_times = tf.random.uniform(
    shape=(batch_size, 1, 1, 1), minval=0.0, maxval=1.0
)
noise_rates, signal_rates = self.diffusion_schedule(diffusion_times)
# mix the images with noises accordingly
noisy_images = signal_rates * images + noise_rates * noises

Reverse Process

Reverse Processは元の論文はかなり複雑ですが、今回のsampleコードではかなりsimpleになり、基本的にnoiseが加えたデータに対して、作成したネットワーク構造で、逆予測し、予測データ(画像)と予測noiseを分解し、徐々に元のデータと近づくような処理を行います。
with tf.GradientTape() as tape:
            # train the network to separate noisy images to their components
            pred_noises, pred_images = self.denoise(
                noisy_images, noise_rates, signal_rates, training=True
            )

            noise_loss = self.loss(noises, pred_noises)  # used for training
            image_loss = self.loss(images, pred_images)  # only used as metric

        gradients = tape.gradient(noise_loss, self.network.trainable_weights)
        self.optimizer.apply_gradients(zip(gradients, self.network.trainable_weights))

        self.noise_loss_tracker.update_state(noise_loss)
        self.image_loss_tracker.update_state(image_loss)
def denoise(self, noisy_images, noise_rates, signal_rates, training):
       # the exponential moving average weights are used at evaluation
       if training:
           network = self.network
       else:
           network = self.ema_network

       # predict noise component and calculate the image component using it
       pred_noises = network([noisy_images, noise_rates**2], training=training)
       pred_images = (noisy_images - noise_rates * pred_noises) / signal_rates

       return pred_noises, pred_images

画像生成の例

基本的に、画像生成するコードは生成できます。

 
def diffusion_schedule(diffusion_times, min_signal_rate, max_signal_rate):
    start_angle = tf.acos(max_signal_rate)
    end_angle = tf.acos(min_signal_rate)

    diffusion_angles = start_angle + diffusion_times * (end_angle - start_angle)

    signal_rates = tf.cos(diffusion_angles)
    noise_rates = tf.sin(diffusion_angles)

    return noise_rates, signal_rates


def generate_images(diffusion_steps, stochasticity, min_signal_rate, max_signal_rate):
    step_size = 1.0 / diffusion_steps
    initial_noise = tf.random.normal(shape=(num_images, image_size, image_size, 3))

    # reverse diffusion
    noisy_images = initial_noise
    for step in range(diffusion_steps):
        diffusion_times = tf.ones((num_images, 1, 1, 1)) - step * step_size
        next_diffusion_times = diffusion_times - step_size

        noise_rates, signal_rates = diffusion_schedule(diffusion_times, min_signal_rate, max_signal_rate)
        next_noise_rates, next_signal_rates = diffusion_schedule(next_diffusion_times, min_signal_rate, max_signal_rate)

        sample_noises = tf.random.normal(shape=(num_images, image_size, image_size, 3))
        sample_noise_rates = stochasticity * (1.0 - (signal_rates / next_signal_rates) ** 2) ** 0.5 * (
                next_noise_rates / noise_rates)

        pred_noises, pred_images = model([noisy_images, noise_rates, signal_rates])
        noisy_images = (
                next_signal_rates * pred_images
                + (next_noise_rates ** 2 - sample_noise_rates ** 2) ** 0.5 * pred_noises
                + sample_noise_rates * sample_noises
        )

    # denormalize
    data_mean = tf.constant([[[[0.4705, 0.3943, 0.3033]]]])
    data_std_dev = tf.constant([[[[0.2892, 0.2364, 0.2680]]]])
    generated_images = data_mean + pred_images * data_std_dev
    generated_images = tf.clip_by_value(generated_images, 0.0, 1.0)

    # make grid
    generated_images = tf.image.resize(generated_images, (plot_image_size, plot_image_size), method="nearest")
    generated_images = tf.reshape(generated_images, (num_rows, num_cols, plot_image_size, plot_image_size, 3))
    generated_images = tf.transpose(generated_images, (0, 2, 1, 3, 4))
    generated_images = tf.reshape(generated_images, (num_rows * plot_image_size, num_cols * plot_image_size, 3))
    return generated_images.numpy()
以下のコードで、画像生成するために、最初の値(noise)が完全にランダムになって、その後、花の形にきれいに生成されることわかります。
<code>initial_noise = tf.random.normal(shape=(num_images, image_size, image_size, <span class="hljs-number">3</span>))</code>
各step数の生成する画像の質の比較は以下です。

diffusion step=1


diffusion step=10


diffusion step=80

まとめ

今回、確率モデル+ニューラルネットワーク(hybrid model)の一つ、Diffusionモデルを紹介しました。直近は生成モデルとしてはかなり精度が良くて、AIの分野でも注目度が高いです。実サービスでは、基本的に表形式のデータが多いため、普通の使い方(生成モデル)に比べて、工夫が必要だと思います。また、Diffusionモデル自体は元になるネットワークの構造が自由に設定できるので、色んなデータ形式のお応用も今後増えて行くではないかと思います。

 

最後に

グループ研究開発本部 AI研究開発室では、データサイエンティスト/機械学習エンジニアを募集しています。ビッグデータの解析業務などAI研究開発室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。皆さんのご応募をお待ちしています。

 

参考:

https://lilianweng.github.io/posts/2021-07-11-diffusion-models/

https://arxiv.org/pdf/2006.11239v2.pdf

https://arxiv.org/pdf/2112.10752.pdf

https://github.com/CompVis/stable-diffusion

https://paperswithcode.com/paper/denoising-diffusion-probabilistic-models

https://github.com/divamgupta/stable-diffusion-tensorflow

https://github.com/hojonathanho/diffusion

https://towardsdatascience.com/diffusion-models-made-easy-8414298ce4da

https://medium.com/@vedantjumle/image-generation-with-diffusion-models-using-keras-and-tensorflow-9f60aae72ac

https://keras.io/examples/generative/ddim/

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事