生成モデル三国志、最後の流派。Normalizing Flowをnumpyで一から実装したら、一番静かに学習が終わった
約7分で読めます

生成モデル三国志、最後の流派。Normalizing Flowをnumpyで一から実装したら、一番静かに学習が終わった


拡散モデルは「ノイズを少しずつ取り除く」流派だった。GANは「偽物職人と鑑定士を騙し合わせる」流派だった。生成モデルにはもう1つ、教科書で必ず並ぶ第3の流派がある。Normalizing Flow —— 「可逆な変換だけを積み重ねて、ガウス分布をデータの形に変形する」流派だ。

シリーズの締めとして、これもnumpyだけで一から実装し、前2回と全く同じtwo moonsデータ・同規模の手書きMLPで、三国志を完結させることにした。

仕組み: どちらの向きにも流せる変換の鎖

Normalizing Flowの構造図。左のガウス雲と右のtwo moonsデータの間に6つの層が並び、上向きの赤い矢印(生成: zを順に変形)と下向きの青い矢印(学習・尤度計算: xをzに戻す)が両方向に描かれている

Flowの本体は「アフィンカップリング層」の積み重ねだ。各層は座標の片方(例えばx座標)をそのまま通し、それを入力とする小さなMLPが出力する係数で、もう片方(y座標)を伸縮・平行移動する。

# 順方向 (x -> z): 学習と尤度計算に使う
s, t = MLP(x_a)                 # 通す側の座標から係数を計算
y_b  = x_b * exp(s) + t         # もう片方を伸縮+平行移動
# 逆方向 (z -> x): 生成に使う。厳密に逆算できる
x_b  = (y_b - t) * exp(-s)

片方を触らず残すおかげで、逆変換が厳密に書ける。しかも変換のヤコビアン(体積の伸縮率)が対角になるので、対数尤度 logp(x)\log p(x) が展開の連鎖で厳密に計算できる。「学習=尤度の最大化」という統計学の正攻法が、ニューラルネットでそのまま使えるわけだ。層を6枚(伸縮方向を交互に)重ね、30,000イテレーション学習させた。

ガウス雲が、6段階でtwo moonsになる

学習後の変換を、ガウス雲側から1層ずつ通して見る。まずは動画で(各点の色は出発時の角度。どの点がどこへ運ばれるかを追える)。

そして今回から新しい試みとして、スライダーで自分の手で層を進められる版も用意した。動画では一瞬で通り過ぎる「層3→4の劇的な変形」を、好きなところで止めてじっくり見てほしい。

学習後の検証で、逆変換の再構成誤差 maxxf1(f(x))\max|x - f^{-1}(f(x))|2.07×10⁻¹⁴。浮動小数点の丸め誤差レベル、つまり完全に可逆だ。拡散モデルの生成は同じノイズから毎回違う結果が出るし、GANには逆向きの変換がそもそも存在しない。「行って戻れる」はFlowだけの性質になる。

Flowだけの特権: 「この点の確率は?」に即答できる

可逆で体積変化が追えるということは、平面上の任意の点の確率密度を厳密に計算できるということだ。学習済みFlowに平面全体を問い合わせて、確率密度の地図を作った。

学習済みFlowが計算した厳密な確率密度の地図。two moonsの2つの三日月が明るく浮かび、輪郭に沿って密度が滑らかに減衰している。左上には「密度が薄く残った三日月間のブリッジ」への注釈矢印がある

拡散モデルもGANも「サンプルを出す」ことはできるが、「この点の密度はいくつ?」には答えられない(近似はできるが厳密には無理)。この地図が描けるのはFlowだけだ。異常検知(密度が低い=異常)にFlowが使われるのは、まさにこの性質のためだ。

正直に書くと、地図には弱点も写っている。三日月の間に薄い密度の「ブリッジ」が残っている(注釈の矢印)。連続で可逆な変形だけでガウス雲(ひとつながり)を2つの島に完全に分けることは位相的にできないので、必ずどこかに細い橋が残る。可逆性という美徳が、そのまま表現力の制約になる——Flowの光と影が1枚に入った。

三国志、そろい踏み

同じtwo moons・同規模の手書きMLPで作った3流派を、本物と並べる。

2x2の比較図。本物のデータ(グレー)、拡散モデル(緑)は輪郭がややにじむが2つの三日月をはっきり再現、GAN(青)は対角方向の煙状の分布で三日月の形が崩れている、Flow(赤)は輪郭のはっきりした三日月を再現している

  • 拡散モデル: 構造をはっきり再現(やや滲む)。学習は「ノイズ推定の回帰」で安定
  • GAN: 損失は理論値に収束したのに、分布は最後まで揃わなかった(前回の主題)
  • Flow: 輪郭のはっきりした三日月。学習中の損失(負の対数尤度)は4.39→2.38へ、一度も暴れずに単調に下がった

手を動かして意外だったこと

一番印象的だったのは、学習の静けさだ。GANの実装では生成器と識別器の駆け引きが振動し、収束しても分布が揃わないドラマがあった。Flowは同じ課題を、敵対もサンプリング反復もなしに、ただ尤度を上げるだけで淡々とこなした。実装上の小さな工夫(最終層の重みをゼロ初期化して「恒等変換」から出発する)も効いていて、初日から損失が発散せず、素直に下るだけの学習だった。派手さと引き換えに、目的関数が本当に最適化したいもの(データの尤度)そのものであることの強さを感じる。

一方で、可逆性の代償も体感できた。次元を増やせない(2次元は2次元のまま)、ひとつながりの分布を千切れさせられない、層ごとの表現力が制限される。3流派は「生成」という同じゴールに対して、何を犠牲にするかの選択がそれぞれ違う。拡散は生成の速さを、GANは学習の安定と尤度を、Flowは表現の自由度を差し出している。全部を同じ土俵で動かしたからこそ、この三すくみの構図が腹に落ちた。

まとめ

  • 生成モデル第3の流派Normalizing Flow(RealNVP型アフィンカップリング6層)をnumpyで実装し、拡散モデルGANと同じtwo moonsデータで学習させた
  • 逆変換の再構成誤差は2.07×10⁻¹⁴(機械精度)——「行って戻れる」生成モデルであることを数値で確認
  • Flowだけの特権として、平面上の全点の厳密な確率密度の地図を作成。ただし三日月間に位相的に避けられない密度のブリッジが残る(可逆性の代償)
  • 3流派の同条件比較: Flow・拡散は構造を再現、GANは形が崩れたまま。Flowの学習は負の対数尤度4.39→2.38へ一度も暴れず単調に収束し、GANと対照的に「静か」だった
  • 見せ方の新しい試み: 動画(mp4)と、読者が自分で層を進められるスライダー付きインタラクティブを併設した