結論

拡散モデル(diffusion model)とは、データに少しずつノイズを足して最後は完全なノイズにする過程を考え、その逆向き、つまり「ノイズを少しずつ取り除く」手順をニューラルネットワークに学ばせて、ノイズから新しいデータを作る生成モデルです。Stable Diffusion をはじめ、近年の多くの画像生成AIの土台になっています。

この記事でわかること

  • 「ノイズを足す過程」と「ノイズを予測して戻す過程」の仕組みと、DDPM の4つの式の意味を、2次元の点で数値を見ながら読む方法
  • numpy だけで書いた拡散モデル(3万6千パラメータ)の学習・生成のコードと、scikit-learn との一致(差 0.00)、生成した点が「月の形」の上に乗る割合 99.1% という実測
  • ステップ数・ノイズのスケジュール・学習量・ガイダンスの強さで生成結果がどう変わるかの表と、画像生成AI(Stable Diffusion など)の仕組みを論文・公式の範囲で整理した内容

2026年11月5日更新|Python 3.13.18・numpy 2.5.3・SciPy 1.18.1・scikit-learn 1.9.1(OpenBLAS、スレッド数1)・CPU 2コアで実行確認

この記事で実際に動かしたもの:numpy だけで書いた DDPM(ノイズを足す過程・ノイズを予測する小さなネットワークとその逆伝播・Adam・生成の手順)と DDIM、分類器なしガイダンス。データは scikit-learn の make_moons で作った2次元の点(2,000個)で、比べる相手に scikit-learn の MLPRegressor と GaussianMixture を使いました。PyTorch・TensorFlow・GPU・学習済みの画像生成モデルは使っていません。

flowchart TD
    A[元のデータ x0
月の形の点] -->|ノイズを少し足す
を200回| B[完全なノイズ xT
正規分布] B -->|予測したノイズを
少し引く を200回| C[新しいデータ
月の形の点] D[学習 足したノイズを
ネットワークに当てさせる] -.-> C

よく調べられることの答えを先にまとめます。

知りたいこと答え詳しい節
拡散モデルとはノイズを足す過程の逆をたどり、ノイズから新しいデータを作る生成モデル。2020年の DDPM で画像の品質が大きく上がった拡散モデルとは
diffusion model の仕組み学習では「どれだけノイズを足したか」を当てさせるだけ。生成では、予測したノイズを少しずつ引く手順を何百回か繰り返す仕組み①・仕組み②
拡散モデルの式(DDPM)の意味x_t = √ᾱ_t·x_0 + √(1−ᾱ_t)·ε の1本で、どの時刻のノイズ入りデータも一度に作れる。損失は足したノイズとの二乗誤差DDPMの式の意味
拡散モデルを python で自作できるか2次元の点なら numpy だけで書ける。この記事のコードで、生成した点の 99.1% が月の形の上に乗ったnumpyで自作する
ステップ数を減らすとどうなるかDDIM なら 200回 → 20回でも形は保てるが、分布のずれ(SW)は 0.021 → 0.078 に増えたステップ数・スケジュール
ガイダンス(CFG)とは条件ありと条件なしの予測の差を強めて、条件に合わせる力を上げる方法。強くするほど多様さが減る条件付き生成とガイダンス
画像生成AI の仕組みStable Diffusion は画像を 1/8 の大きさの潜在空間に圧縮し、そこで文章を条件にした拡散モデルを動かす(潜在拡散モデル)画像生成AIの仕組み
GAN・VAE との違いGAN は1回で生成して速いが学習が不安定になりやすい。拡散モデルは学習が安定する代わりに生成に何十〜何百回の計算が要るGAN・VAEとの違い

確認環境:Ubuntu 24.04.5 LTS(Intel Xeon 2.10GHz・2コア・メモリ8GB の共有のマシン)で、Python 3.13.18・numpy 2.5.3(OpenBLAS 0.3.34、OPENBLAS_NUM_THREADS=1)・SciPy 1.18.1・scikit-learn 1.9.1 を使いました。データは scikit-learn の make_moons(2,000点・noise=0.05)を平均0・標準偏差1にそろえたもので、ネットからは何も取得していません。生成した点の評価には、別の乱数の種で作った本物の点2,000個を使いました。学習は合計で約3分、プロセスの最大メモリは250MB でした。記事に載せたコードは本文から抜き出して実行し直し、表と同じ値(同じ重み)になることを確かめています。式とアルゴリズムは DDPM・DDIM・Improved DDPM・分類器なしガイダンスの各論文、Stable Diffusion の構成は潜在拡散モデルの論文と CompVis の公式リポジトリの README で確かめた範囲だけを書いています。画像生成AIの製品どうしの優劣は扱いません。図は mermaid で描画を確かめています(確認日 2026年11月5日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「拡散モデルとは|ノイズを足して戻す仕組みを2次元で自作・画像生成AIの仕組み」

拡散モデルとは(diffusion modelを1行で・画像生成AIとの関係)

この節では、拡散モデルという言葉の意味と、ふだんの「拡散」との違い、生成モデルの中での位置づけが分かります。

拡散モデルは「データを壊す手順」を決めておき、その逆の「直す手順」を学ぶことで、何もないノイズから新しいデータを作る生成モデルです。生成モデルとは、学習したデータと「同じような」新しいデータを作るモデルのことで、分類や回帰のように答えを1つ当てるモデルとは目的が違います。画像・音声・動画・分子の構造などの生成に使われています。

「拡散」は、ふだんの言葉としても使われるため、検索すると別の意味の結果が混じります。最初に並べておきます。

言葉ふだんの意味機械学習(IT)での意味
拡散インクが水に広がる・SNS で情報が広まることデータに少しずつノイズを足して、形のない正規分布へ広げていく過程(物理の拡散現象から名前を借りている)
ノイズ雑音・じゃまな音平均0の正規分布から引いた乱数。わざと足すもので、モデルはこれを当てるように学ぶ
ステップ階段の1段・ダンスの足運びノイズを足す(取り除く)1回分。DDPM では1,000回、この記事では200回
スケジュール予定表各ステップでどれだけノイズを足すかの並び(β の列)。線形・コサインなどがある
ガイダンス案内・指導生成を条件(文章やラベル)に強く従わせる計算の工夫。強さを数値で決める
シード植物の種乱数の種。最初のノイズが決まるので、同じシードなら同じ画像(点)になる手順がある

拡散モデルを理解するうえで押さえる考え方は、次の3つです。この記事では3つとも2次元の点で確かめます。

1壊す手順は学習しない

ノイズを足す過程は、あらかじめ決めた式どおりに動くだけ。学習するパラメータは無い。

2学ぶのは「足されたノイズ」

ノイズ入りのデータと時刻を見せて、足したノイズを当てさせる。損失はただの二乗誤差で、分類の学習と同じくらい単純。

3生成は小さく直すのを何度も

一気に元へ戻すのは難しいが、少しのノイズを取り除くのは易しい。これを何百回も重ねて、ノイズをデータに変える。

拡散モデルの考え方は Sohl-Dickstein らの2015年の論文が始まりで、Ho・Jain・Abbeel の2020年の論文「Denoising Diffusion Probabilistic Models(DDPM)」が、ノイズを当てる単純な損失で高品質な画像を作れることを示して広まりました。DDPM の論文は、CIFAR10 で FID 3.17・Inception score 9.46 と報告しています。この記事のコードも DDPM の形に従います。ニューラルネットワーク自体の仕組み(重み・活性化関数・誤差逆伝播)は、関連記事「ニューラルネットワークとは|仕組み・学習・AttentionをNumPyで実演」で扱っています。

最初の5分:2次元の点にノイズを足してみる

この節では、numpy と scikit-learn だけで「月の形の点」にノイズを足していく最小の手順と、その結果の数値が分かります。

画像は何十万もの数(画素)の集まりですが、拡散モデルの仕組みは数が2つの「点」でも同じです。2次元なら、元の形が崩れていく様子と、戻った形が正しいかを数えて確かめられます。make_moons は、上向きと下向きの2つの三日月が組み合わさった形の点を作る関数です。

import numpy as np
from sklearn.datasets import make_moons

X, _ = make_moons(n_samples=2000, noise=0.05, random_state=0)
x0 = (X - [0.5, 0.25]) / [0.87, 0.50]        # 平均0・標準偏差1にそろえる

T = 200
betas = np.linspace(5e-4, 0.1, T)              # 1ステップで足すノイズの量
abar = np.cumprod(1 - betas)                   # 元の点がどれだけ残るか
rng = np.random.default_rng(0)
print("t   残る割合  標準偏差(横,縦)  先頭の点")
for t in [0, 10, 50, 100, 199]:
    eps = rng.standard_normal(x0.shape)
    xt = np.sqrt(abar[t]) * x0 + np.sqrt(1 - abar[t]) * eps
    print(f"{t+1:<4}{np.sqrt(abar[t]):.3f}     {xt.std(0).round(2)}     {xt[0].round(2)}")
t   残る割合  標準偏差(横,縦)  先頭の点
1   1.000     [1. 1.]     [-0.21  1.35]
11  0.984     [1.01 1.  ]     [-0.06  1.39]
51  0.716     [1.01 1.02]     [ 0.26 -0.01]
101 0.270     [0.99 1.01]     [ 0.25 -0.42]
200 0.006     [0.98 1.  ]     [0.74 0.12]

「残る割合」(√ᾱ_t)は 1.000 から 0.006 まで下がり、200ステップ目では元の点はほとんど残っていません。一方で、標準偏差はずっと 1 のままです。元の点を縮めながら、縮めた分だけノイズを足しているため、全体の広がりは変わりません。DDPM のこの形は「分散を保つ(variance preserving)」と呼ばれ、データを先に標準偏差1にそろえておく理由はここにあります。

点の分布を文字で描くと、次のようになりました(濃い記号ほど点が多い。左が元のデータ、右下がこの記事のモデルで生成した点)。

[元のデータ t=0]                          [ノイズ途中 t=100]
        ...:..:..                      .  .  . ....::.::...... .. .. .
     .:###*#****##:.                      .. .....:...:......... .    .. .
    ##:..        .**#.                 ..... .::....:*:.:...:. ...... . .
  .##       ..     .:#*         ...     .....:....:.:..:......::..... .. ..
 .#*.       #*       :*:        .#:    .......:..*.:.:::.*:.*::.:...... ...
 :*:       .*#.      .##..      *#:    ..........:..:.....:*........:.. ..
 **.        .*#.      :*.      .#:       ..  .....:.:....::.::.:.::..: .  .
             .##*..   .     ..##:      .. . ..........::.....::..:.:.:.....
               .*##*.. . .:*#*:          .  ...:... .....:::...::.... .. .
                 ..:.##*#*#:.          .    .....  .... ............    .
                                          ...  . ...   ........ . .  .

[生成した点(200ステップでノイズから戻した)]
         ...::...
     .::#:*:#::###..
   .##*.    ..   .:##.
  .#*.    ...:      *#.          ..
 .*#.      .#*       :*:         #:
 :#:        :*       .#*       .:#.
 ::.        .##      .::.  .   :#:
             .#**.    ...    :*#.
               :*#*#... ..:###..
                 . ######*#:.
                        .

この記事では、生成した点の良し悪しを次の2つの数で測ります。どちらも本物の点(別の乱数で作った2,000個)を基準にします。

  • 月の上の割合:点からノイズの無い三日月の線までの距離が 0.15(make_moons のノイズの標準偏差 0.05 の3倍)未満の点の割合。本物は 99.7%、ただの正規分布の乱数は 33.0%
  • SW(スライス・ワッサースタイン距離):2つの点の集まりを64通りの向きに投影し、1次元の分布の差(SciPy の wasserstein_distance)を平均したもの。小さいほど分布が近い。本物どうしで 0.009、正規分布の乱数で 0.213

月の上の割合だけでは「片方の月にしか点が無い」ような偏りを見逃すため、上下どちらの月に近いかの割合(下の月の割合、理想は 50%)も合わせて見ます。確率分布・正規分布の基本は、関連記事「確率分布とは|種類一覧・平均と分散・選び方をPythonで実演」で扱っています。

拡散モデルの仕組み①:ノイズを足す過程(順方向)とスケジュール

この節では、ノイズを足す過程(順方向・拡散過程)の式と、1ステップずつ足した結果と一度に作った結果が同じになることの確認、スケジュールの選び方が分かります。

順方向の過程は、1ステップごとに次の計算をします。β_t は「このステップで足すノイズの量」で、0.0005 〜 0.1 のような小さな数です。

x_t = √(1 − β_t) · x_{t−1} + √β_t · ε (ε は標準正規分布の乱数)

これを t 回繰り返すと、正規分布の性質(正規分布の和はまた正規分布)により、途中を飛ばして一度に書けます。

x_t = √ᾱ_t · x_0 + √(1 − ᾱ_t) · ε (ᾱ_t = (1−β_1)(1−β_2)…(1−β_t))

この「一度に作れる」性質のおかげで、学習では好きな時刻 t のノイズ入りデータを1回の計算で作れます。200回の繰り返しと、一度に作る式で、点の広がりが同じになるかを確かめました。

時刻 t√ᾱ_t1ステップずつ200回(標準偏差)一度に作る式(標準偏差)
100.9861.003・0.9971.000・1.001
500.7250.995・1.0021.014・0.991
1000.2771.010・1.0041.016・0.988
2000.0061.004・1.0020.988・1.025

違いは乱数の揺れ(2,000点の標準偏差の誤差はおよそ ±0.016)の範囲に収まりました。

スケジュール(β の並べ方)で何が変わるか

DDPM の論文は T=1000、β を 0.0001 から 0.02 まで直線で増やす「線形スケジュール」を使っています。Nichol と Dhariwal の Improved DDPM(2021年)は、ᾱ_t がコサインの形でなだらかに下がる「コサインスケジュール」を提案しました。いくつかの設定で、全体の 10%・25%・50%・75%・100% の時刻に元の点が残る割合(√ᾱ_t)を計算しました。

設定10%50%75%最後(100%)
線形 T=1000(DDPM の論文の値)0.9470.2800.0580.0064
線形 T=200・β はそのまま0.9900.7760.5660.3636
線形 T=200・β を5倍0.9490.2770.0550.0055
コサイン T=2000.9860.7030.3800.0002
線形 T=50・β を20倍0.9550.2630.0470.0028

2つのことが分かります。1つめは、ステップ数 T だけを減らして β をそのままにすると、最後でも元の点が 36% 残り、「完全なノイズ」にならないことです。T を変えるときは β も合わせて大きくします。2つめは、コサインは途中まで元の点を多く残し、終わり近くで急に消すことです。順方向の途中で、点がどれだけ月の形を保っているかを数えました(コサイン T=200)。

時刻 t√ᾱ_t月の上の割合SW(本物との差)SW(正規分布との差)
11.00099.8%0.0110.215
110.99592.9%0.0160.207
260.97763.7%0.0440.185
510.91742.5%0.0900.153
1010.69734.0%0.1630.077
2000.00030.7%0.2250.034

√ᾱ_t が 0.98 でも、月の上の割合は 64% まで下がっています。2次元の月の幅は細いため、少しのノイズで形が崩れます。線形(β 5倍)では t=26 で 42.4% と、もっと早く崩れました。早い時刻で崩しすぎると、ネットワークが細かい形を学ぶ時間が短くなるのが、コサインが提案された理由の1つです(後の節で、生成の結果の差を実測します)。

拡散モデルの仕組み②:ノイズを予測して戻す過程(逆方向)

この節では、逆方向の過程で何を学習し、生成のときにどんな計算を繰り返すのかが分かります。

逆方向は「x_t を見て、1つ前の x_{t−1} を作る」手順です。β が小さければ、この逆の1ステップもほぼ正規分布で表せることが知られていて、その平均をネットワークに予測させます。DDPM は、平均を直接予測させるかわりに「x_t に含まれているノイズ ε」を予測させる形にすると、損失がただの二乗誤差になり、学習もうまくいくことを示しました。

1

学習:データを1つ選び、時刻 t とノイズ ε を引く

t は 1〜T から一様に、ε は標準正規分布から。

DDPM の Algorithm 1 の2〜4行目

2

学習:一度に作る式で x_t を作り、ネットワークに ε を当てさせる

入力は x_t と t。出力 ε_θ と本物の ε の二乗誤差を小さくするよう重みを更新する。

これを何千〜何十万回繰り返す

3

生成:正規分布の乱数 x_T から始める

シード(乱数の種)が同じなら、同じ出発点になる。

Algorithm 2 の1行目

4

生成:予測したノイズを少し引き、少しだけ新しいノイズを足す

t=T から 1 まで繰り返す。最後のステップだけはノイズを足さない。

この記事では200回で約1秒(2,000点)

生成の途中で、点がどう月の形に戻っていくかを数えました(コサイン T=200、4,000回学習したモデル)。

残りの時刻 t月の上の割合SW(本物との差)標準偏差(横・縦)
199(1ステップ目の後)17.4%0.6751.79・2.00
15031.7%0.1991.02・0.98
10034.4%0.1730.98・0.98
5045.1%0.0980.99・0.98
2564.1%0.0541.00・0.98
1078.8%0.0371.00・0.98
0(完成)87.1%0.0311.00・0.98

形が現れるのは終わりの50ステップほどで、順方向で形が崩れた時刻とちょうど対応しています。1ステップ目の後だけ広がりが 1.8〜2.0 に膨らんでいるのは、コサインスケジュールの最後の β を 0.999 で打ち切っているため、最初の1回で √α_t ≈ 0.03 で割る計算が入るからです。次のステップで広がりは 1 に戻りました。

ネットワークが時刻ごとにどれだけノイズを当てられているか(二乗誤差)も測りました(12,000回学習したモデル、各4,000点)。

時刻 t√ᾱ_tノイズ予測の二乗誤差読み方
11.0000.995ノイズが小さすぎて、データ自体の揺れと区別できない
210.9850.592月の線からのずれでノイズを推測できる
1010.6970.438形の大まかな位置が手がかり
1510.3730.135x_t のほとんどがノイズなので当てやすい
2000.0000.001x_t はノイズそのもの

何も分からずに 0 と答えると二乗誤差は 1 になるため、t=1 の 0.995 は「ほぼ当てられない」状態です。ただし、この時刻で引くノイズの量はごく小さいため、生成への影響も小さくなります。

DDPMの式の意味を1つずつ読む

この節では、DDPM の論文に出てくる4つの式(一度に作る式・損失・生成の1ステップ・元のデータの推定)の意味と、コードとの対応が分かります。

式は記号が多いので、言葉に置き換えて読みます。論文は時刻を 1〜T で数えますが、この記事のコードは 0〜T−1 で数えます(配列の番号に合わせるため)。

1

x_t = √ᾱ_t·x_0 + √(1−ᾱ_t)·ε

元のデータを √ᾱ_t 倍に縮め、√(1−ᾱ_t) 倍のノイズを足す。2つの係数の二乗の和はいつも1。

コードの q_sample

2

L = ‖ε − ε_θ(x_t, t)‖²

足したノイズと、予測したノイズの二乗誤差。t は一様に選ぶ。DDPM は L_simple と呼ぶ。

回帰の MSE と同じ形

3

x_{t−1} = (x_t − β_t/√(1−ᾱ_t)·ε_θ) / √α_t + σ_t·z

予測したノイズのうち、このステップの分だけを引き、少し大きさを戻し、新しい乱数 z を σ_t の分だけ足す。

コードの sample_ddpm

4

x̂_0 = (x_t − √(1−ᾱ_t)·ε_θ) / √ᾱ_t

式1を x_0 について解いたもの。今の予測から「最終的な完成形」を推定できる。

DDIM・画像の途中表示で使う

式3の σ_t には2通りの選び方があり、DDPM の論文は σ_t² = β_t と、σ_t² = β̃_t = (1−ᾱ_{t−1})/(1−ᾱ_t)·β_t のどちらでも似た結果だったと書いています。手元でも、同じモデルで前者が月の上の割合 85.6%、後者が 87.1% と近い値でした。

もう1つの見方として、ノイズの予測 ε_θ は「その場所で、データの密度が高くなる向き」(スコア、∇log p(x))と −√(1−ᾱ_t) 倍の関係にあります。Song らのスコアベースの生成モデル(2019年・2021年)はこの見方から同じ仕組みを導いていて、拡散モデルは「ノイズを当てる学習」と「密度の坂を登る生成」の2つの言い方で説明されます。

ノイズを当てる見方(DDPM)

ネットワークは足されたノイズ ε を予測する

損失は二乗誤差で、実装が単純

生成は式3を T 回繰り返す

確率モデルの対数尤度の下限から導く

スコアの見方(スコアベース・SDE)

ネットワークは密度の勾配(スコア)を予測する

ノイズの予測と定数倍で同じもの

生成は確率微分方程式を解く計算として表せる

DDIM などの速い生成手順を整理しやすい

numpyで拡散モデルを自作する(ネットワークと学習のコード)

この節では、ノイズを予測するネットワーク(全結合3層・ReLU)と、時刻の埋め込み、学習のループのコードが分かります。

ネットワークには、ノイズ入りの点 x_t(2個の数)と、時刻 t を16個の数に広げたもの(サイン・コサインの埋め込み)を合わせた18個の数を入れ、ノイズの予測(2個の数)を出させます。中間は128個の3層で、パラメータは 35,714 個です。時刻をネットワークに伝えないと、どれだけノイズが入っているかが分からず、うまく戻せません(後の節で実測します)。

import numpy as np

def cosine_betas(T, s=0.008):                  # Improved DDPM のコサインスケジュール
    f = lambda t: np.cos((t / T + s) / (1 + s) * np.pi / 2) ** 2
    t = np.arange(T + 1)
    ab = f(t) / f(0)
    return np.clip(1 - ab[1:] / ab[:-1], 0, 0.999)

class Sched:
    def __init__(self, betas):
        self.b = betas; self.T = len(betas)
        self.a = 1 - betas; self.ab = np.cumprod(self.a)

def q_sample(s, x0, t, eps):                   # 式1:好きな時刻の x_t を一度に作る
    ab = s.ab[t][:, None]
    return np.sqrt(ab) * x0 + np.sqrt(1 - ab) * eps

def temb(t, T, d=16):                          # 時刻をサイン・コサインの16個の数にする
    f = np.exp(np.linspace(0, np.log(1000), d // 2))
    ang = (t / T)[:, None] * f[None, :]
    return np.concatenate([np.sin(ang), np.cos(ang)], 1)

class MLP:                                     # 全結合・ReLU・手書きの逆伝播と Adam
    def __init__(self, sizes, seed=0):
        r = np.random.default_rng(seed)
        self.W = [r.normal(0, np.sqrt(2 / a), (a, b)) for a, b in zip(sizes[:-1], sizes[1:])]
        self.B = [np.zeros(b) for b in sizes[1:]]
        self.m = [np.zeros_like(p) for p in self.W + self.B]
        self.v = [np.zeros_like(p) for p in self.W + self.B]; self.k = 0

    def forward(self, x):
        self.hs = [x]
        for i, (W, B) in enumerate(zip(self.W, self.B)):
            x = x @ W + B
            if i < len(self.W) - 1: x = np.maximum(x, 0)
            self.hs.append(x)
        return x

    def backward(self, g):                     # g は損失を出力で微分したもの
        gW, gB = [], []
        for i in reversed(range(len(self.W))):
            h = self.hs[i]
            gW.insert(0, h.T @ g); gB.insert(0, g.sum(0))
            if i > 0:
                g = (g @ self.W[i].T) * (self.hs[i] > 0)
        return gW, gB

    def adam(self, gW, gB, lr, b1=0.9, b2=0.999, e=1e-8):
        self.k += 1
        for j, (p, g) in enumerate(zip(self.W + self.B, gW + gB)):
            self.m[j] = b1 * self.m[j] + (1 - b1) * g
            self.v[j] = b2 * self.v[j] + (1 - b2) * g * g
            mh = self.m[j] / (1 - b1 ** self.k); vh = self.v[j] / (1 - b2 ** self.k)
            p -= lr * mh / (np.sqrt(vh) + e)

def train(s, x0, steps=4000, bs=256, lr=2e-3, seed=0):
    r = np.random.default_rng(seed)
    net = MLP([18, 128, 128, 128, 2], seed)
    for it in range(1, steps + 1):
        idx = r.integers(0, len(x0), bs)                  # データを選ぶ
        t = r.integers(0, s.T, bs)                        # 時刻を一様に選ぶ
        eps = r.standard_normal((bs, 2))                  # 足すノイズ
        xt = q_sample(s, x0[idx], t, eps)
        out = net.forward(np.concatenate([xt, temb(t, s.T)], 1))
        diff = out - eps                                  # 式2:ノイズとの差
        gW, gB = net.backward(2 * diff / diff.size)
        net.adam(gW, gB, lr * 0.5 * (1 + np.cos(np.pi * it / steps)))
    return net

逆伝播が正しいかは、数値微分(重みを ±0.00001 動かしたときの損失の差)と比べて確かめました。3層から15個の重みを選んで比べた結果、最大の相対誤差は 8.6e-10、中央値は 6.0e-11 で、浮動小数点の丸めの範囲で一致しました。逆伝播と数値微分の考え方は、上で紹介したニューラルネットワークの記事と同じです。

生成のコードは、式3をそのまま書いたものです。

def sample_ddpm(s, f, n, seed=1):              # f(x, t) はノイズの予測を返す関数
    r = np.random.default_rng(seed)
    x = r.standard_normal((n, 2))                         # x_T:正規分布から始める
    for t in reversed(range(s.T)):
        e = f(x, t)
        mean = (x - s.b[t] / np.sqrt(1 - s.ab[t]) * e) / np.sqrt(s.a[t])
        if t > 0:
            sig2 = s.b[t] * (1 - s.ab[t - 1]) / (1 - s.ab[t])   # σ_t² = β̃_t
            x = mean + np.sqrt(sig2) * r.standard_normal((n, 2))
        else:
            x = mean                                      # 最後はノイズを足さない
    return x

def eps_fn(net, T):
    return lambda x, t: net.forward(np.concatenate([x, temb(np.full(len(x), t), T)], 1))

# 使い方
# s = Sched(cosine_betas(200)); net = train(s, x0, steps=12000)
# xs = sample_ddpm(s, eps_fn(net, 200), 2000)
35,714ネットワークのパラメータ数18→128→128→128→2
27.5秒12,000回の学習CPU 1スレッド・ミニバッチ256
0.96秒2,000点の生成200ステップ

学習の時間は、同じ 4,000回でも 9〜31秒 と揺れました。共有のマシンで同時に動く処理の量によって変わるためで、表の時間は目安です。

scikit-learnとの一致と、生成した点の分布を数値で比べる

この節では、自作のネットワークが scikit-learn の MLPRegressor と同じ計算をしていることの確認と、拡散モデル・混合ガウスモデル・正規分布の乱数で作った点を、同じ物差しで比べた結果が分かります。

scikit-learn には拡散モデルそのものはありませんが、ノイズを予測する部分は「入力18個から2個の数を当てる回帰」なので、MLPRegressor(全結合のニューラルネットワーク)で置き換えられます。まず、自作のネットワークの重みを MLPRegressor にそのまま写し、予測が一致するかを見ました。

from sklearn.neural_network import MLPRegressor

m2 = MLPRegressor(hidden_layer_sizes=(128, 128, 128), activation="relu", max_iter=1)
m2.fit(inp[:300], e[:300])                     # 形を決めるために一度だけ fit する
m2.coefs_ = [W.copy() for W in net.W]
m2.intercepts_ = [B.copy() for B in net.B]
tst = inp[:5000]
print("max|numpy-sklearn| = %.2e" % np.abs(net.forward(tst) - m2.predict(tst)).max())
max|numpy-sklearn| = 0.00e+00

5,000件の入力で差は完全に 0 でした。自作の順伝播(行列の積・バイアス・ReLU、出力層は恒等関数)は、MLPRegressor と同じ計算をしています。次に、MLPRegressor 自体を学習させ(データ2,000点を60倍に増やし、時刻とノイズを引いた12万行、12エポック・18.1秒)、自作と同じ生成の手順で点を作りました。

比べる相手として、scikit-learn の GaussianMixture(混合ガウスモデル)も使いました。正規分布をいくつか重ねて分布を表す、昔からある生成モデルで、sample() で新しい点を作れます。

作り方SW(小さいほど良い)月の上の割合下の月の割合学習の時間
本物(別の乱数の種)0.00999.7%50.0%―
正規分布の乱数0.21333.0%48.0%―
混合ガウス 2個0.11340.5%50.2%1秒未満
混合ガウス 8個0.03297.9%50.0%1秒未満
混合ガウス 32個0.02499.8%50.5%1秒未満
拡散モデル(線形・4,000回)0.04574.3%50.5%31秒
拡散モデル(コサイン・1,000回)0.07052.0%50.7%2秒
拡散モデル(コサイン・4,000回)0.03187.1%50.6%27秒
拡散モデル(コサイン・12,000回)0.02199.1%51.4%28秒
拡散モデル(MLPRegressor)0.07996.9%47.1%18秒

読み取れることは3つです。

  • 学習量で大きく変わる:コサインで 1,000回 → 4,000回 → 12,000回と増やすと、月の上の割合は 52.0% → 87.1% → 99.1%、SW は 0.070 → 0.031 → 0.021 になった
  • 同じ学習量なら、コサインが線形より良かった:4,000回で月の上の割合 87.1% と 74.3%。順方向で形を早く崩しすぎない効果と合う
  • 1つの物差しだけでは判断できない:MLPRegressor の版は月の上の割合が 96.9% と高いのに、SW は 0.079 と悪い。上下の月の割合が 47:53 に偏るなど、分布全体の形がずれている

そして、2次元の点という小さな問題では、混合ガウス32個(1秒未満)が拡散モデルと同じかそれ以上の結果でした。拡散モデルが強いのは、画像のように次元が何十万もあり、正規分布をいくつ重ねても表せないような複雑な分布です。2次元の実験は仕組みを確かめるためのもので、手法の優劣を決めるものではありません。

「生成した点が学習データの丸写しになっていないか」も確かめました。生成した点から最も近い学習データまでの距離の中央値は 0.021 で、別に作った本物の点の 0.018 と同じくらいでした。距離 0.001 未満の点は 2,000点中 4点で、別に作った本物の点でも 3点あったため、偶然の範囲です。点の「新しさ」を測る考え方は、画像では学習データの記憶(memorization)の研究として扱われています。

ステップ数・スケジュール・学習量で結果はどう変わるか(DDIMで速くする)

この節では、生成のステップ数を減らす方法(DDIM)と、減らしたときの速さと品質の関係、減らすときにつまずく点が分かります。

DDPM の生成は、学習した T と同じ回数(論文では1,000回)ネットワークを呼びます。Song・Meng・Ermon の DDIM(Denoising Diffusion Implicit Models、2020年)は、同じ学習済みモデルのまま、途中の時刻を飛ばして生成できる手順で、論文は DDPM より実時間で10〜50倍速いと報告しています。考え方は、式4で「完成形 x̂_0」を推定し、それを式1で次の時刻まで戻す、を繰り返すことです。乱数を足さない形(eta=0)では、同じ出発点から必ず同じ結果になります。

def sample_ddim(s, f, n, steps, seed=1, clip=3):
    r = np.random.default_rng(seed)
    x = r.standard_normal((n, 2))
    ts = np.linspace(s.T - 1, 0, steps).round().astype(int)    # 飛ばして使う時刻
    for i, t in enumerate(ts):
        e = f(x, t)
        x0 = (x - np.sqrt(1 - s.ab[t]) * e) / np.sqrt(s.ab[t])   # 式4:完成形の推定
        if clip: x0 = np.clip(x0, -clip, clip)
        if i + 1 < len(ts):
            ab2 = s.ab[ts[i + 1]]
            x = np.sqrt(ab2) * x0 + np.sqrt(1 - ab2) * e        # 式1で次の時刻へ
        else:
            x = x0
    return x

12,000回学習したコサインのモデルで、生成の手順と回数を変えました(2,000点)。

手順・回数生成の時間SW月の上の割合下の月の割合
DDPM 200回0.96秒0.02199.1%51.4%
DDIM 200回0.95秒0.02197.7%50.6%
DDIM 100回0.54秒0.02598.0%51.2%
DDIM 50回0.23秒0.04797.9%51.6%
DDIM 20回0.10秒0.07898.1%52.2%
DDIM 10回0.05秒0.11793.1%52.7%
DDIM 5回0.03秒0.14483.1%56.9%

生成の時間はネットワークを呼ぶ回数にほぼ比例しました。20回まで減らしても点は月の上に乗りますが、SW は 0.021 → 0.078 に増え、上下の割合も少しずつ偏ります。点が「それらしい形」になっていても、分布の広がり方は崩れ始めていることが、数で見ると分かります。画像生成AIの設定にある「ステップ数」は、この回数のことです。

コサインスケジュールで DDIM を使うとき

やってはいけない完成形の推定 x̂_0 をそのまま使う。最初の時刻は √ᾱ_t が 0.0002 しかなく、それで割った値が大きく外れる
→
代わりにデータの範囲(ここでは ±3)で x̂_0 を切り詰める(clip)か、最初の時刻を少し手前にずらす

手元では 50回で、切り詰め無しは SW 0.360・月の上 46.9%、切り詰めありは SW 0.047・97.9%

ステップ数 T を変えるとき

やってはいけないT だけを1,000から200に減らし、β の範囲は論文の 0.0001〜0.02 のまま使う
→
代わりに最後の √ᾱ_T が0に近くなるよう β を大きくするか、コサインのように ᾱ_t で決めるスケジュールを使う

β をそのままにすると、最後でも元の点が 36% 残り、学習と生成の出発点がずれる

なお、β をそのままにした T=200 のモデルも学習して生成しましたが、2次元の点では月の上の割合 88.1% と、崩れはしませんでした。出発点のずれ(元の点が 0.36 倍だけ残った分布と、純粋な正規分布の差)が、2次元では小さかったためです。画像では、最後の時刻にデータが少し残るこのずれが、明るさの偏りにつながることが Lin らの論文(2023年)で指摘されています。手元で崩れなかったからといって正しい設定とは言えない例として載せておきます。

条件付き生成とガイダンス(分類器なしガイダンス)の考え方

この節では、「上の月だけ作って」のように条件を付けて生成する方法と、分類器なしガイダンス(CFG)の式、ガイダンスを強くしたときに起きることが分かります。

画像生成AIで「猫の写真」と文章を入れると猫が出てくるのは、ネットワークに条件(文章)も入力しているからです。2次元では、make_moons のラベル(0=上の月、1=下の月)を条件にします。ネットワークの入力に「上・下・条件なし」の3つのどれかを表す数を足し、学習のときは20%の確率で条件を「なし」に置き換えます。こうすると1つのネットワークで、条件ありの予測 ε_θ(x_t, c) と条件なしの予測 ε_θ(x_t, ∅) の両方を出せます。

Ho と Salimans の分類器なしガイダンス(Classifier-Free Guidance、2022年)は、この2つを次のように組み合わせます。

ε̃ = (1 + w) · ε_θ(x_t, c) − w · ε_θ(x_t, ∅)

w=0 なら条件ありの予測そのまま、w を大きくすると「条件なしから条件ありへ向かう差」を強めて、条件らしさを強調します。それ以前の Dhariwal と Nichol(2021年)の分類器ガイダンスは、別に学習した分類器の勾配で生成を押す方法で、CFG は分類器を不要にしたものです。論文は、条件付きと無条件のモデルを同時に学習し、品質と多様さのトレードオフを分類器ガイダンスと同じように調整できると述べています。

def cond_input(x, t, T, c):                    # c: 0=上の月, 1=下の月, 2=条件なし
    oh = np.zeros((len(x), 3)); oh[:, c] = 1
    return np.concatenate([x, temb(np.full(len(x), t), T), oh], 1)

def eps_cfg(net, T, c, w):                     # 分類器なしガイダンス
    def f(x, t):
        e_c = net.forward(cond_input(x, t, T, c))
        e_u = net.forward(cond_input(x, t, T, 2))
        return (1 + w) * e_c - w * e_u
    return f

# 学習では、ミニバッチのラベル c のうち20%を「条件なし」に置き換える
# c[r.random(bs) < 0.2] = 2

条件付きのモデル(4,000回・9.4秒)で、上の月(c=0)を条件に w を変えて1,000点ずつ生成しました。「自分の月のカバー率」は、上の月の線上に等間隔に置いた点のうち、0.1 以内に生成した点がある割合で、多様さ(月の端まで作れているか)の目安です。

ガイダンスの強さ w正しい月に出た割合月の上の割合自分の月のカバー率標準偏差(横・縦)
0(条件ありの予測のみ)100%98.8%100%0.82・0.63
1100%99.8%99.5%0.71・0.55
3100%99.9%94.0%0.61・0.50
7100%99.4%88.5%0.56・0.43

2次元の月は条件が分かりやすいため、w=0 でもすべて正しい月に出ました。w を上げると月の上の割合はわずかに上がりますが、点の広がり(標準偏差)は 0.82 → 0.56 と縮み、月の端まで届かなくなります(カバー率 100% → 88.5%)。条件に合った「典型的な」点に寄る代わりに、多様さが減る、という論文の説明どおりの動きです。下の月(c=1)でも、カバー率は 100% → 93.5% と同じ向きでした。

同じモデルに条件なしで生成させると、月の上の割合は 85.2% で、条件を付けたとき(98.8%)より下がりました。条件は「どちらの月か」を教えるだけですが、2つの月の間の迷いが無くなる分、形がはっきりします。

途中から戻す(画像を元にした生成・強さの設定)

画像生成AIの「元の画像から作る(image-to-image)」と「ノイズ除去の強さ(strength)」も、同じ仕組みで説明できます。元のデータに時刻 strength×T までノイズを足し、そこから戻すだけです。月を上に 0.3 ずらした点(月の上の割合 28.6%)を入力にして試しました。

強さ戻し始める時刻月の上の割合入力から動いた平均の距離
0.12094.5%0.226
0.36098.8%0.515
0.510098.8%0.820
0.816098.5%1.186
1.020099.1%1.257

強さ 0.1 では、点はずらした量(0.3)と同じくらい動いて、最も近い月に戻りました。強さを上げるほど元の位置は忘れられ、1.0 では入力と関係のない点になります。「元の画像の構図を残したいなら強さを下げる」という使い方の理由が、数で分かります。

画像生成AIの仕組み(Stable Diffusionなどを論文・公式の範囲で)

この節では、2次元の点で確かめた仕組みが、画像生成AIではどう大きくなっているのかを、論文と公式のリポジトリで確かめた範囲で説明します。製品どうしの比較や優劣は扱いません。

画像を直接拡散モデルで扱うと、512×512 のカラー画像なら1枚で約79万個の数を、何百回もネットワークに通すことになり、計算が非常に重くなります。Rombach らの潜在拡散モデル(Latent Diffusion Models、CVPR 2022)は、先に学習したオートエンコーダで画像を小さな「潜在」に圧縮し、その潜在の空間で拡散モデルを動かすことで計算を減らしました。文章などの条件は、cross-attention という層でネットワークに取り込みます。オートエンコーダの考え方(圧縮して元に戻す)は、主成分分析と近い部分があり、関連記事「主成分分析(PCA)とは|手順・寄与率・負荷量の読み方をPythonで実演」で扱っています。

Stable Diffusion(v1)は、この潜在拡散モデルを文章から画像を作る用途で学習したものです。CompVis の公式リポジトリの README に書かれている構成は次のとおりです。

部品README の記述2次元の実験での対応
オートエンコーダ縮小率8(downsampling-factor 8)。512×512 の画像なら縦横 64×64 の潜在になる計算無し(点をそのまま扱った)
ノイズを予測するネットワーク860M(約8.6億)パラメータの U-Net35,714 パラメータの全結合3層
条件(文章)の読み取りCLIP ViT-L/14 のテキストエンコーダ(123M)上・下・なしの3つの印
学習データLAION-5B の一部。256×256 で事前学習し、512×512 で追加学習make_moons の2,000点
ガイダンス学習時にテキストの条件を10%落とす(分類器なしガイダンスのため)。サンプリングの既定は --scale 7.520%を条件なしにして学習、w=0〜7
生成のステップ参照スクリプトは PLMS サンプラーで50ステップ。DDIM も選べ、eta=0 で決定的になるDDPM 200回・DDIM 5〜200回

README の --scale 7.5 は、条件なしの予測に「条件ありとの差」の 7.5 倍を足す書き方の値で、この記事の w((1+w) 倍と w 倍の書き方)に直すと w=6.5 に当たります。生成の流れをまとめると次のようになります。

  1. 1文章をテキストエンコーダで数の列にするプロンプトが条件 c になる。条件なしの予測には空の文章を使う
  2. 2シードから潜在の大きさの乱数を作る2次元の実験の「正規分布から始める」と同じ。シードが同じなら出発点も同じ
  3. 3U-Net でノイズを予測し、ガイダンスで組み合わせる条件ありと条件なしの2通りを計算して、式で組み合わせる
  4. 4サンプラーで1ステップ戻す、を数十回繰り返すDDIM などの手順。ステップ数の設定はこの回数
  5. 5オートエンコーダのデコーダで潜在を画像に戻す潜在の空間から画素の空間へ。ここで初めて画像になる

その後の研究では、ノイズを予測するネットワークを U-Net から Transformer に置き換える流れもあります。Esser らの論文「Scaling Rectified Flow Transformers for High-Resolution Image Synthesis」(2024年、Stable Diffusion 3 の論文)は、データとノイズを直線で結ぶ rectified flow という定式化と、画像と文章で別々の重みを持つ Transformer を使い、高解像度の文章からの画像生成で既存の拡散モデルの定式化より良い結果だったと報告しています。「少しずつノイズを取り除いて作る」という骨組みは同じで、何を予測させるか・どの道筋で戻すか・どのネットワークを使うかが変わってきている、と整理できます。

文章を条件にする部分は、大規模言語モデルと同じく文章を数の列(トークンの埋め込み)にして扱います。生成AI全体の仕組みとの関係は、関連記事「生成AI(LLM)の仕組み8選|ハルシネーション・トークン」で扱っています。

拡散モデルとGAN・VAEの違い

この節では、代表的な生成モデル(GAN・VAE・フローベース・自己回帰)と拡散モデルの違いを、論文で示されている仕組みの範囲で比べた表が分かります。

生成モデル作り方学習のしかた生成の計算よく言われる弱み
拡散モデル(2015・2020〜)ノイズから少しずつノイズを取り除く足したノイズとの二乗誤差。安定しやすいネットワークを数十〜数百回呼ぶ生成が遅い(DDIM・蒸留などで改善の研究が多い)
GAN(2014〜)生成器がノイズから1回で作る本物か偽物かを見分ける識別器と競わせる1回学習が不安定になりやすく、一部の種類しか作らなくなる(モード崩壊)ことがある
VAE(2013〜)潜在の乱数をデコーダで1回で戻す復元の誤差と、潜在の分布を正規分布に近づける項1回画像がぼやけやすいとされる
フローベース逆に戻せる変換を重ねて、正規分布をデータに変える対数尤度を直接最大化1回(変換の数だけ)逆に戻せる形に制約され、ネットワークの設計が限られる
自己回帰1つずつ順番に次を予測する(文章の LLM と同じ)次の要素の交差エントロピー要素の数だけ画像では要素(画素・トークン)が多く、順番に作るぶん時間がかかる

拡散モデルと VAE は、どちらも「正規分布の乱数をデータに変える」点が似ています。違いは、VAE がそれを1回の変換で学ぶのに対し、拡散モデルは何百回の小さな変換に分け、しかも途中の分布(順方向の各時刻)を式で決めてしまう点です。中間を決めてあるので、各ステップの学習は「少しのノイズを当てる」易しい問題になります。損失関数の考え方(二乗誤差・交差エントロピー)の違いは、関連記事「エントロピーとは|意味・計算・交差エントロピーをPythonで実演」で扱っています。この記事で使った Adam などの最適化の手順は、本サイトの「勾配降下法とは|学習率・SGD・Adamを自作して収束を比較」でも扱う予定です。

拡散モデルを自作するときのよくあるエラー・つまずき

この節では、この記事のコードを書く途中で実際に出たエラーと、結果がおかしくなった原因と直し方が分かります。

症状(原文・数値)原因直し方
ValueError: operands could not be broadcast together with shapes (256,) (256,2)時刻ごとの係数 √ᾱ_t(256個)を、点の配列(256×2)にそのまま掛けたs.ab[t][:, None] のように列の形にしてから掛ける
IndexError: index 200 is out of bounds for axis 0 with size 200論文の時刻 1〜T をそのまま配列の番号に使った配列は 0〜T−1。乱数は integers(0, T) で引く
時刻を入れずに学習すると、月の上の割合 55.6%・SW 0.198ネットワークがノイズの量を知らないため、どの時刻でも同じ答えしか出せない時刻の埋め込みを入力に足す(この記事では16個の数)
DDIM で SW 0.360、点が ±6 まで飛ぶコサインの最初の時刻で √ᾱ_t ≈ 0.0002 で割っているx̂_0 をデータの範囲で切り詰める
学習の損失が 4.6億 まで増える(学習率 1.0)学習率が大きすぎて重みが発散したAdam なら 1e-3 前後から。損失の推移を記録して見る
Stochastic Optimizer: Maximum iterations (12) reached and the optimization hasn't converged yet.MLPRegressor の max_iter が少なく、収束の前に止めた(警告で、結果は使える)時間の許す範囲で max_iter を増やすか、損失の推移を見て止めどころを決める
点が月の上には乗るが、上下の割合が 47:53 に偏る学習が足りない・モデルが小さい。1つの物差しでは見逃す形(月の上の割合)と分布の差(SW)の両方を測る

自作するときの確認の順番を、チェックリストにしました。

判断表:拡散モデルを使う・使わない

この節では、データを作りたい・増やしたいときに、拡散モデルを選ぶか、ほかの方法にするかの判断の目安が分かります。

状況向く方法理由
数個〜数十個の列の表データ・低次元の点を作りたい混合ガウスなどの簡単な生成モデルから試す手元の2次元では、混合ガウス32個が1秒未満で拡散モデルと同等以上だった
画像・音声のような高次元で複雑なデータを作りたい拡散モデル(多くは潜在拡散)正規分布の重ね合わせでは表せない分布を、安定した学習で表せる
1回の生成を速く(リアルタイムに)したいステップ数を減らした拡散モデル、または1回で作るモデル(GAN など)拡散モデルはステップ数に比例して時間がかかる。手元でも回数にほぼ比例した
文章やラベルの条件どおりに作りたい条件付きの拡散モデル+分類器なしガイダンス1つのネットワークで条件の強さを調整できる。強すぎると多様さが減る
既存のデータを少し変えたい(構図を残す)途中の時刻から戻す(image-to-image)強さ(何割の時刻からか)で、元をどれだけ残すかを決められる
仕組みを学びたい・検定試験の勉強をしたいこの記事のように2次元で自作する数十秒の学習で、式の意味を数で確かめられる

深層学習の検定試験の出題範囲には、生成モデルも含まれています。試験の内容と要点は、関連記事「E資格とは|試験内容・合格率・G検定との違いとNumPyで解く要点」で扱っています。

よくある質問

この節では、拡散モデルについて検索されることの多い質問への答えが分かります。

Q. 拡散モデルはなぜ「拡散」と呼ぶのですか。

データにノイズを少しずつ足していく過程が、インクが水に広がる物理の拡散現象(粒子がランダムに動いて広がる)と同じ形の式で書けるからです。最初の論文(Sohl-Dickstein ら、2015年)が、非平衡の熱力学の考え方から作られたことに由来します。

Q. 拡散モデルとディープラーニングはどう違いますか。

ディープラーニングは、層を深く重ねたニューラルネットワーク全体を指す言葉です。拡散モデルは「ノイズを足して戻す」という生成の枠組みで、その中のノイズを予測する部分にニューラルネットワーク(U-Net や Transformer)を使います。

Q. 学習で何を当てさせているのですか。

ノイズ入りのデータと時刻を見せて、「足したノイズ」そのものを当てさせています(DDPM の場合)。損失は二乗誤差です。元のデータ x_0 を当てさせる形や、Salimans と Ho(2022年)の速度(v)を当てさせる形もあり、式で互いに変換できます。

Q. ステップ数はいくつにすればよいですか。

学習の T は DDPM の論文では1,000です。生成のステップ数は DDIM などの手順で減らせ、Stable Diffusion v1 の参照スクリプトの既定は50ステップです。手元の2次元では20回まで形は保てましたが、分布の差は200回の約4倍になりました。速さと品質のどちらを優先するかで決めます。

Q. シードを固定すると同じ画像になるのはなぜですか。

生成の出発点の乱数(と途中で足す乱数)がシードで決まるからです。乱数を足さない DDIM(eta=0)では、出発点が同じなら結果も必ず同じになり、手元でも同じシードで2回生成した結果が完全に一致しました。

Q. ガイダンスの値を大きくするとどうなりますか。

条件に合わせる力が強くなる代わりに、多様さが減ります。手元では w を 0 → 7 にすると、点の広がりが 0.82 → 0.56、月の端まで作れている割合が 100% → 88.5% に下がりました。

Q. 拡散モデルは学習データをそのまま覚えて出しているのですか。

手元の2次元では、生成した点と学習データの距離は、別に作った本物の点と同じくらいで、丸写しの点は偶然の範囲でした。ただし大きなモデルでは、学習データに近いものを出してしまう場合があることが Carlini らの論文(2023年)などで報告されていて、確かめ方の研究が続いています。

Q. GPU が無くても拡散モデルを動かせますか。

この記事の2次元の例は、CPU 1スレッドで学習28秒・生成1秒でした。画像の拡散モデルは、ネットワークが数億パラメータあり、それを数十回呼ぶため、実用的な速さには GPU がほぼ必須です。

Q. 拡散モデルは画像以外にも使えますか。

使えます。仕組みはデータの次元に依らないため、この記事のような点、音声、動画、時系列、分子やたんぱく質の構造などの生成にも研究・利用が広がっています。

まとめ

この節では、記事全体の要点が分かります。

  1. 拡散モデルは、ノイズを足す過程の逆をたどってデータを作る生成モデル。壊す手順は式で決めておき、学ぶのは「足されたノイズ」だけ。損失は二乗誤差。
  2. DDPM の要は x_t = √ᾱ_t·x_0 + √(1−ᾱ_t)·ε の1本。どの時刻のノイズ入りデータも一度に作れ、1ステップずつ足した結果と一致した。T を変えたら β も見直す(そのままだと最後に 36% 残る)。
  3. numpy の自作(3万6千パラメータ)で、生成した点の 99.1% が月の上に乗った。重みを写した MLPRegressor とは差 0.00 で一致。学習量・スケジュールで結果は大きく変わり、2次元では混合ガウスでも同等以上になる。
  4. ステップ数は DDIM で減らせるが、分布は少しずつ崩れる。200回 → 20回で時間は約10分の1、SW は 0.021 → 0.078。コサインでは x̂_0 の切り詰めが要る。
  5. 条件付き生成とガイダンスは「条件らしさ」と「多様さ」の調整。画像生成AI(Stable Diffusion)は、これを潜在空間・U-Net・テキストエンコーダで大きくしたもので、骨組みは2次元の点と同じ。

参考・出典

この節では、この記事で根拠にした論文・公式ドキュメントが分かります(確認日 2026年11月5日)。

  • J. Ho, A. Jain, P. Abbeel「Denoising Diffusion Probabilistic Models」NeurIPS 2020:https://arxiv.org/abs/2006.11239
  • J. Sohl-Dickstein ほか「Deep Unsupervised Learning using Nonequilibrium Thermodynamics」ICML 2015:https://arxiv.org/abs/1503.03585
  • J. Song, C. Meng, S. Ermon「Denoising Diffusion Implicit Models」2020:https://arxiv.org/abs/2010.02502
  • A. Nichol, P. Dhariwal「Improved Denoising Diffusion Probabilistic Models」2021:https://arxiv.org/abs/2102.09672
  • P. Dhariwal, A. Nichol「Diffusion Models Beat GANs on Image Synthesis」2021:https://arxiv.org/abs/2105.05233
  • J. Ho, T. Salimans「Classifier-Free Diffusion Guidance」2022:https://arxiv.org/abs/2207.12598
  • Y. Song ほか「Score-Based Generative Modeling through Stochastic Differential Equations」ICLR 2021:https://arxiv.org/abs/2011.13456
  • R. Rombach ほか「High-Resolution Image Synthesis with Latent Diffusion Models」CVPR 2022:https://arxiv.org/abs/2112.10752
  • S. Lin ほか「Common Diffusion Noise Schedules and Sample Steps are Flawed」2023:https://arxiv.org/abs/2305.08891
  • N. Carlini ほか「Extracting Training Data from Diffusion Models」2023:https://arxiv.org/abs/2301.13188
  • T. Salimans, J. Ho「Progressive Distillation for Fast Sampling of Diffusion Models」2022:https://arxiv.org/abs/2202.00512
  • CompVis「stable-diffusion」公式リポジトリ README:https://github.com/CompVis/stable-diffusion
  • P. Esser ほか「Scaling Rectified Flow Transformers for High-Resolution Image Synthesis」2024:https://arxiv.org/abs/2403.03206
  • I. Goodfellow ほか「Generative Adversarial Networks」2014:https://arxiv.org/abs/1406.2661
  • D. P. Kingma, M. Welling「Auto-Encoding Variational Bayes」2013:https://arxiv.org/abs/1312.6114
  • scikit-learn 1.9.1 公式ドキュメント「make_moons」「MLPRegressor」「GaussianMixture」:https://scikit-learn.org/stable/api/index.html
  • SciPy 公式ドキュメント「scipy.stats.wasserstein_distance」:https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.wasserstein_distance.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「拡散モデルとは|ノイズを足して戻す仕組みを2次元で自作・画像生成AIの仕組み」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり