FiLM(Feature-wise Linear Modulation)とは?条件で特徴を変調する仕組みを実装で理解する

ニューラルネットワークに「条件」を与えたい場面は数多くあります。クラスを指定して画像を生成したい、動作モードを踏まえて異常を判定したい、テキストで画像生成を制御したい——こうした条件付け(conditioning)の最も素朴な方法は、条件ベクトル $\bm{c}$ を入力に連結(concat)することでした(条件付きVAEで使った方法です)。しかし連結には弱点があります。条件は入口で一度だけ混ざるだけで、ネットワークが深くなると条件の影響が薄まりやすいのです。

そこで登場するのが FiLM(Feature-wise Linear Modulation) です。FiLMは、条件 $\bm{c}$ からスケール $\bm{\gamma}$シフト $\bm{\beta}$ を生成し、ネットワークの中間特徴 $\bm{h}$ を

$$ \bm{h}’ = \bm{\gamma}(\bm{c}) \odot \bm{h} + \bm{\beta}(\bm{c}) $$

という形で変調します。たったこれだけの操作で、条件がネットワークの各層・各チャネルに直接介入できるようになり、連結よりも強力で柔軟な条件付けが実現します。

この記事では次の問いに答えます。

  • 問い1: 「特徴を変調する」とは具体的にどういう操作で、なぜ連結より強力なのか?
  • 問い2: 条件付き正規化やStyleGAN、拡散モデルの時刻埋め込みといった有名手法と、FiLMはどう繋がっているのか?

FiLM(Perezら, 2018)はもともと、画像に対する自然言語の質問——「左の物体は何色?」のような——に答える視覚的質問応答(VQA)で提案されました。質問文に応じて画像のどの特徴に注目すべきかが変わるため、「質問(条件)で画像特徴を変調する」という発想が自然に生まれたのです。その後、この「条件で特徴を変調する」アイデアの汎用性が見いだされ、条件付き生成・スタイル変換・強化学習・マルチモーダルなど幅広い領域で使われる基本部品になりました。本記事ではその仕組みを、2D合成データでのスクラッチ実装まで含めて完全に理解します。

FiLM:条件cからγ,βを生成し特徴を変調

全体像は図の通りです。条件 $\bm{c}$ を小さなネットワークに通して $\bm{\gamma}(\bm{c})$ と $\bm{\beta}(\bm{c})$ を作り、それで特徴 $\bm{h}$ を $\bm{h}’=\bm{\gamma}\odot\bm{h}+\bm{\beta}$ と変調する——これがFiLMのすべてです。では、この単純な式がなぜ効くのかを掘り下げます。

前提知識

この記事は条件付けの基本(連結)と生成モデルを前提にします。不安なら先に次を読むとスムーズです。

まずは「特徴を変調する」という操作の中身を、具体的に見ていきます。

FiLMの仕組み — 特徴をスケール&シフトする

FiLMの中核は、特徴ベクトル $\bm{h} \in \mathbb{R}^{d}$ の各成分(チャネル)を、条件から決まる係数でアフィン変換することです。$j$ 番目のチャネルについて書くと、

$$ \begin{equation} h’_j = \gamma_j(\bm{c}) \cdot h_j + \beta_j(\bm{c}) \end{equation} $$

です。$\gamma_j$ は乗算(スケール)、$\beta_j$ は加算(シフト)で、どちらも条件 $\bm{c}$ の関数です。重要なのは、$\bm{\gamma}, \bm{\beta}$ が特徴と同じ次元 $d$ を持ち、チャネルごとに別々の変調ができる点です。

FiLMの作用:γでスケール、βでシフト

図がFiLMの作用です。元の特徴(左)に対し、$\bm{\gamma}$ を掛けると(中央)、各チャネルが選択的にスケールされます。特に $\gamma_j=0$ ならそのチャネルは完全に遮断され、$\gamma_j$ が大きいチャネルは強調されます。さらに $\bm{\beta}$ を足すと(右)、全体がシフトします。つまりFiLMは「条件に応じて、どの特徴を活かし、どの特徴を殺すか」を制御できるのです。乗算による選択的なゲーティングが、単なる加算(連結が実質行うこと)より表現力が高い理由です。

なぜ乗算による変調が効くのか

FiLMの本質は「乗算 $\bm{\gamma}\odot\bm{h}$」にあります。これがなぜ重要かを、連結と数式で比べると明快です。

条件 $\bm{c}$ を入力 $\bm{x}$ に連結して全結合層 $W$ に通すと、

$$ W\begin{bmatrix}\bm{x}\\\bm{c}\end{bmatrix} = W_x\bm{x} + W_c\bm{c} $$

となり、$\bm{x}$ の寄与 $W_x\bm{x}$ と $\bm{c}$ の寄与 $W_c\bm{c}$ が加算的に分離します。つまり連結における条件は、本質的に「出力のバイアスを $W_c\bm{c}$ だけずらす」効果しか持ちません。特徴 $\bm{x}$ と条件 $\bm{c}$ が掛け合わさる項は作れないのです。

一方FiLMの $\bm{\gamma}(\bm{c})\odot\bm{h}$ は、条件 $\bm{c}$ と特徴 $\bm{h}$ の——双線形な相互作用——を直接表現します。これにより「条件によって、ある特徴の重要度そのものが変わる」という効果が書けます。たとえば「機器をONにする操作が行われたときだけ温度特徴に強く注目する」は、温度特徴 × 操作条件の積でしか表現できません。加算的な連結では、この「条件依存の重み付け」が原理的に作れないのです。これがFiLMが連結より表現力で勝る数学的な理由です。

具体例でもう一押ししましょう。「温度が高い」という特徴 $h_{\text{temp}}$ と「加熱機器のON/OFF」という条件があり、判定したいのは「ONなら温度上昇は正常、OFFなら異常」だとします。これは $h_{\text{temp}}$ の重要度(さらには符号)が条件で変わる状況で、$\gamma_{\text{temp}}(\bm{c})$ を条件によって大小・符号まで変えれば自然に表現できます。連結による加算項 $W_c\bm{c}$ は出力を一律にずらすだけで、「温度特徴の重みそのものを条件で切り替える」ことはできません。条件と特徴の”掛け算”が要る問題こそ、FiLMの出番です。

連結(concat)との違い

この差は、ネットワーク全体で見るとさらに大きくなります。

条件注入の違い:連結 vs FiLM

連結(左)は、条件 $\bm{c}$ を入力 $\bm{x}$ にくっつけてネットワークに渡します。条件が混ざるのは入口で一度だけで、層を経るうちにその情報は他の特徴と混ざって薄まりがちです。一方FiLM(右)は、条件を $\bm{\gamma},\bm{\beta}$ という変調パラメータに変換し、任意の層の中間特徴に直接介入します。深いネットワークでも、各層で条件を効かせ続けられるのがFiLMの強みです。しかも乗算 $\bm{\gamma}\odot\bm{h}$ は、条件と特徴の相互作用を表現できます(連結+線形層では加算的な相互作用しか作れません)。

もう一つの実用的な利点は、条件の次元と本体ネットワークの構造を切り離せることです。連結では条件ベクトルが大きいほど入力次元が膨らみ、最初の層のパラメータが増えます。FiLMでは、条件は生成器を通って $\bm{\gamma},\bm{\beta}$(特徴と同じ固定次元)に変換されるため、条件がどれだけ高次元でも本体ネットワークの構造は変わりません。多数のメタデータをまとめて条件にしたいときに、この性質が効いてきます。

条件パラメータの生成

$\bm{\gamma}(\bm{c})$ と $\bm{\beta}(\bm{c})$ は、条件 $\bm{c}$ を入力とする小さなネットワーク(多くは1層の全結合)で生成します。

条件パラメータ生成器 c→(γ,β)

図のように、条件 $\bm{c}$ を全結合層に通して、変調したい特徴と同じ次元の $\bm{\gamma}$ と $\bm{\beta}$ を出力します。実装では「$\bm{\gamma}$ 用」「$\bm{\beta}$ 用」の2つの線形層を用意するだけです。この生成器も本体のネットワークと一緒に学習され、「どの条件のとき、どのチャネルをどう変調すべきか」を自動的に学びます。

実装上の工夫: γの初期化。 $\bm{\gamma}$ をゼロ付近に初期化すると、学習初期に特徴がほぼ消えてしまい学習が不安定になります。そこで実務では、$\bm{\gamma}$ を 1付近に初期化する(あるいは $\bm{h}’ = (1+\Delta\bm{\gamma}(\bm{c}))\odot\bm{h} + \bm{\beta}(\bm{c})$ と書いて $\Delta\bm{\gamma}$ をゼロ初期化する)のが定石です。こうすると初期状態が「恒等変換+わずかな変調」となり、まず素の特徴を学んでから徐々に条件の効きを強める、という安定した学習ができます。

CNNでのFiLM

FiLMは画像でも使われます。畳み込み特徴マップ $\bm{h}\in\mathbb{R}^{C\times H\times W}$ に対しては、チャネル $C$ ごとに1つの $\gamma_c, \beta_c$ を割り当て、空間方向 $(H,W)$ には同じ値を共有して適用します。

$$ h’_{c,i,j} = \gamma_c(\bm{c})\cdot h_{c,i,j} + \beta_c(\bm{c}) $$

つまり1つの条件で「どの特徴マップ(チャネル)を強調/抑制するか」を制御します。チャネルは「エッジ」「色」「テクスチャ」などの検出器に対応するので、FiLMは「条件に応じてどの視覚的特徴に注目するか」を切り替えていることになります。視覚的質問応答(VQA)でFiLMが提案されたのも、まさに「質問文に応じて画像のどの特徴を見るか」を変えたかったからです。仕組みが分かったので、実際にコードで動かしてみましょう。

Pythonでスクラッチ実装する

条件付きVAEと同じ2D合成データ(条件ごとに位置の違う3つのガウス)を使い、デコーダの条件付けをFiLMで行うCVAEを実装します。連結の代わりにFiLMで条件を注入する、という違いに注目してください。

データとFiLM層

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(0)
rng = np.random.default_rng(0)

K, n = 3, 400
centers = np.array([[0, 2.5], [-2.5, -1.5], [2.5, -1.5]], dtype=np.float32)
X = np.vstack([rng.normal(centers[k], 0.5, (n, 2)) for k in range(K)]).astype(np.float32)
y = np.repeat(np.arange(K), n)
C = np.eye(K, dtype=np.float32)[y]      # 条件をone-hot化
Xt, Ct = torch.tensor(X), torch.tensor(C)

class FiLM(nn.Module):
    """条件 c からスケール γ とシフト β を生成し、特徴 h を変調する"""
    def __init__(self, cdim, hdim):
        super().__init__()
        self.to_gamma = nn.Linear(cdim, hdim)   # γ(c)
        self.to_beta = nn.Linear(cdim, hdim)    # β(c)

    def forward(self, h, c):
        return self.to_gamma(c) * h + self.to_beta(c)   # γ⊙h + β

FiLM クラスがFiLMそのものです。to_gammato_beta が条件パラメータ生成器で、forwardself.to_gamma(c) * h + self.to_beta(c) が変調の式 $\bm{\gamma}\odot\bm{h}+\bm{\beta}$ に対応します。たった数行です。

FiLMで条件付けしたVAE

エンコーダは簡単のため連結のまま、デコーダの中間特徴をFiLMで変調します。

class FiLMVAE(nn.Module):
    def __init__(self, xd=2, cd=3, hd=64, zd=2):
        super().__init__()
        self.e1 = nn.Linear(xd + cd, hd)
        self.emu = nn.Linear(hd, zd); self.elv = nn.Linear(hd, zd)
        self.d1 = nn.Linear(zd, hd)
        self.film = FiLM(cd, hd)        # デコーダの特徴を条件で変調
        self.dout = nn.Linear(hd, xd)

    def encode(self, x, c):
        h = F.relu(self.e1(torch.cat([x, c], dim=1)))
        return self.emu(h), self.elv(h)

    def decode(self, z, c):
        h = self.d1(z)
        h = F.relu(self.film(h, c))     # FiLMで条件注入
        return self.dout(h)

    def forward(self, x, c):
        mu, logvar = self.encode(x, c)
        z = mu + torch.exp(0.5 * logvar) * torch.randn_like(mu)
        return self.decode(z, c), mu, logvar

デコーダの decode で、潜在変数を変換した中間特徴 hself.film(h, c) で条件変調しているのがポイントです。条件付きVAEでは torch.cat([z, c]) と連結していた部分が、FiLMによる変調に置き換わっています。

学習と条件付き生成

損失は条件付きVAEと同じく「再構成誤差 + KL正則化」です。

m = FiLMVAE()
opt = torch.optim.Adam(m.parameters(), lr=5e-3)
for ep in range(600):
    opt.zero_grad()
    rx, mu, lv = m(Xt, Ct)
    loss = F.mse_loss(rx, Xt, reduction='sum') - 0.5 * torch.sum(1 + lv - mu.pow(2) - lv.exp())
    loss.backward(); opt.step()

# 条件を指定して生成
m.eval()
with torch.no_grad():
    for k in range(K):
        z = torch.randn(2000, 2); c = torch.zeros(2000, 3); c[:, k] = 1
        print(f"c={k} 生成平均 {m.decode(z, c).numpy().mean(0).round(2)}  中心 {centers[k]}")

実行すると、生成平均は c=0: [-0.04, 2.51]c=1: [-2.48, -1.51]c=2: [2.46, -1.53] となり、真の中心をほぼ正確に再現しました。

トイデータ:条件cごとの2Dガウス

これが学習に使ったトイデータです。3クラスが平面上の別々の位置にあります。

FiLMで条件付き生成

FiLMで条件付けした生成結果です。同じ潜在分布 $\mathcal{N}(\bm{0},\bm{I})$ から出発しながら、条件 $\bm{c}$ を変えるだけで3クラスを正しく作り分けられています。連結(CVAE)と同じ条件付き生成が、FiLMの変調でも実現できることが確認できます。生成平均は真の中心とほぼ一致し($\bm{c}=1$ で $[-2.48,-1.51]$ vs 中心 $[-2.5,-1.5]$)、FiLMが条件を正確に反映していることが数値でも裏づけられます。

学習されたγ・βを覗く

FiLMの面白さは、学習後に「条件ごとにどんな変調をしているか」を直接見られる点です。

学習された条件別のγ・β

各条件 $\bm{c}=0,1,2$ に対する $\bm{\gamma}(\bm{c})$(左)と $\bm{\beta}(\bm{c})$(右)を見ると、条件ごとに異なる変調パターンが学習されています。あるチャネルは条件Aで強調され($\gamma$ 大)、条件Bでは抑制される($\gamma$ 小)——このように、FiLMは「条件に応じて特徴を選択的に使い分ける」ことを自動的に学んでいます。これが連結にはない、変調ならではの解釈性です。実際、$\gamma_j(\bm{c})$ が条件ごとに大きく変わるチャネルは「そのクラスを特徴づける軸」、$\beta_j(\bm{c})$ が動くチャネルは「条件で基準値がずれる軸」と読めます。今回のトイデータでは各クラスが平面上の別位置にあるので、デコーダはこのγ・βでクラスごとの位置の違いを作り出しているわけです。連結では、こうした「条件ごとの内部状態」を取り出して眺めることはできません。

同じzでcを変えると生成が移動

潜在変数 $\bm{z}$ をほぼ固定したまま条件 $\bm{c}$ だけを変えると、生成される点が各クラス位置へ移動します。FiLMの変調がデコーダの出力を制御していることの直接的な証拠です。条件が確かに効いていることが目で確認できました。

ここまでで、FiLMが汎用的な条件付け部品であることを見ました。実は、有名な手法の多くがFiLMの一般形になっています。

FiLMの仲間たち — 条件付き正規化という大きな枠組み

FiLMの「条件から $(\bm{\gamma},\bm{\beta})$ を作って特徴を変調する」という発想は、実は多くの有名手法の背骨になっています。

FiLMの仲間(条件付き正規化)

  • 条件付きBatch Normalization: 通常のバッチ正規化は $\mathrm{BN}(\bm{h}) = \bm{\gamma}\odot\dfrac{\bm{h}-\bm{\mu}}{\bm{\sigma}} + \bm{\beta}$ で、$\bm{\gamma},\bm{\beta}$ は学習する固定パラメータです。条件付きBNは、この $\bm{\gamma},\bm{\beta}$ を条件 $\bm{c}$ から生成します($\bm{\gamma}(\bm{c}),\bm{\beta}(\bm{c})$)。正規化後の特徴をFiLMで変調しているのと同じです。クラス条件付き画像生成で広く使われます。
  • AdaIN(Adaptive Instance Normalization): スタイル変換で使われ、$\mathrm{AdaIN}(\bm{h},\bm{c}) = \sigma(\bm{c})\cdot\dfrac{\bm{h}-\mu(\bm{h})}{\sigma(\bm{h})} + \mu(\bm{c})$ と書けます。コンテンツ特徴 $\bm{h}$ を正規化し、スタイル $\bm{c}$ の統計量 $(\sigma(\bm{c}),\mu(\bm{c}))$ で再スケール・シフトする——これは $\gamma=\sigma(\bm{c}),\ \beta=\mu(\bm{c})$ としたFiLMそのものです。
  • StyleGAN: スタイルベクトルから各層の変調パラメータ(AdaINの $\gamma,\beta$)を生成し、生成画像のスタイルを階層的に制御します。
  • 拡散モデルの時刻埋め込み: ノイズ除去ステップ $t$ を埋め込んで $(\bm{\gamma},\bm{\beta})$ を作り、各層を時刻で変調します。「いまどのノイズレベルにいるか」を全層に伝える役割です。

これらはすべて「正規化のスケール・シフトを条件から動的に生成する」という、FiLMと同じ構造を持っています。式を見比べると、$\gamma\odot(\text{正規化された特徴})+\beta$ の $\gamma,\beta$ を「何から作るか」が違うだけだと分かります。FiLMを理解すれば、これらの手法が一つの枠組みで見渡せるようになります。

条件付けの使い分け:連結・FiLM・クロスアテンション

ここまでで「連結」と「FiLM」を見てきました。条件付けの主要な選択肢を整理しておきましょう。

手法 仕組み 向いている条件 相互作用 コスト
連結(concat) 入力に $\bm{c}$ を結合 低次元・単純な条件 加算的 最小
FiLM 各層で $\bm{\gamma}\odot\bm{h}+\bm{\beta}$ カテゴリ・スカラ、多層で効かせたい 乗算的
クロスアテンション 条件系列を query で参照 系列・可変長(操作イベント列、テキスト) 動的・位置依存

選び方の指針はこうです。条件が1個の固定ベクトル(クラスラベルや動作モード)なら、まず連結を試し、表現力が足りなければFiLMに上げます。FiLMは連結のほぼ上位互換で、追加コストもわずか(線形層2つ)です。条件が可変長の系列(操作イベントの履歴、文章)で、「どの要素にどれだけ注目するか」を動的に決めたいなら、クロスアテンションが必要になります。FiLMの $\bm{\gamma},\bm{\beta}$ は条件全体から1組だけ作られるのに対し、クロスアテンションは入力の各位置ごとに条件の参照先を変えられるからです。逆に言えば、条件が固定ベクトルならクロスアテンションは過剰で、FiLMで十分なことが多いです。

FiLMをどこに・いくつ挿入するか

FiLMは1箇所に限らず、複数の層に挿入できます。深いネットワークで条件を効かせ続けたい場合、各ブロックの後にFiLM層を置くのが定番です(VQAの原論文でも、複数の残差ブロックそれぞれにFiLMを入れています)。

設計上の選択肢は主に2つです。1つは「層ごとに別々の生成器」を持たせ、各層が独自の $\bm{\gamma},\bm{\beta}$ を学ぶ方法。表現力は高いがパラメータが増えます。もう1つは「生成器を共有」し、条件埋め込みを各層で使い回す方法で、軽量です。タスクの複雑さとデータ量を見て選びます。今回の2D実験のように小さなタスクなら、デコーダに1つFiLMを入れるだけで十分でした。

なお、$\bm{\gamma}$ による乗算は「ソフトなゲーティング」と解釈できます。$\gamma_j$ が大きいチャネルは通し、小さいチャネルは絞る——これは「条件に応じてどの特徴を使うかを連続的に選ぶ」操作で、注意機構(アテンション)と発想が通じます。FiLMは「条件 → 特徴の重み付け」を最も軽量に実現した仕組み、と見ることもできます。

応用 — コンテキストで「正常範囲」を変調する

最後に、異常検知への応用を考えます。FiLMは「条件で特徴を変調する」ので、動作モードや環境といったコンテキストを条件にすれば、モデルが扱う”正常範囲”そのものをコンテキストで動かせます

応用:動作モードで正常範囲が変調

図のように、動作モードを条件 $\bm{c}$ としてFiLMで変調すると、モードAでは低い値域が正常、モードBでは高い値域が正常、というように条件付きの正常範囲を1つのモデルで表現できます。これは条件付きVAEの $p(\bm{x}\mid\bm{c})$ と同じ狙いを、より強力な変調機構で実現するものです。連結が「条件を入口で混ぜる」のに対し、FiLMは「各層で条件を効かせ続ける」ので、コンテキストの影響が複雑・多段なケースに向いています。

たとえば産業設備の監視のように、動作モード・環境条件・機器状態といった複数のコンテキストが各処理段階で異なる影響を与える状況では、条件を入口で1回混ぜる連結よりも、各層で変調し続けるFiLMの方が素直にモデル化できます。さらに、学習後の $\bm{\gamma}(\bm{c}),\bm{\beta}(\bm{c})$ を観察すれば「どのコンテキストがどの特徴を強調・抑制したか」を読み取れるため、異常検知の判断根拠を説明する手がかりにもなります。「変化が起きたか」だけでなく「どの文脈でどの特徴が動いたか」まで踏み込めるのは、実運用で重宝する性質です。固定したしきい値では扱えない「文脈に応じて変わる正常性」を、1つのモデルで素直に表現できる点が、FiLMによる条件付けの実用的な強みと言えます。

まとめ

FiLM(Feature-wise Linear Modulation)を、仕組みからスクラッチ実装、関連手法まで通して見てきました。

  • FiLMは条件 $\bm{c}$ から $\bm{\gamma},\bm{\beta}$ を生成し、中間特徴を $\bm{h}’=\bm{\gamma}\odot\bm{h}+\bm{\beta}$ と変調する条件付け機構。
  • 連結が「入口で一度だけ条件を混ぜる」のに対し、FiLMは各層・各チャネルで条件を効かせ続け、乗算による相互作用で表現力が高い。$\gamma=0$ ならチャネルを遮断するなど、選択的なゲーティングができる。
  • 2D合成データで、FiLMによる条件付き生成が連結(CVAE)と同等に機能することを確認した。学習されたγ・βは条件ごとに異なり、解釈もできる。
  • 条件付きBatchNorm・AdaIN・StyleGAN・拡散モデルの時刻埋め込みは、すべてFiLMの一般形。
  • コンテキストを条件にFiLM変調すれば、条件付きの「正常範囲」を表現でき、コンテキスト認識型の異常検知に使える。

  • 実装上は $\bm{\gamma}$ を1付近に初期化して恒等変換から始めると学習が安定し、複数層に挿入して条件を効かせ続けるのが定番。

FiLMの要点を一言で言えば、「条件を、特徴を変調するための $\bm{\gamma},\bm{\beta}$ に翻訳する」ことです。この翻訳さえできれば、対象がMLPでもCNNでも生成モデルでも、同じ部品として差し込めます。連結が条件付けの”入門”なら、FiLMは多くの場面で使える”標準装備”。条件と特徴の乗算的な相互作用を最小コストで手に入れられること、そして学習後のγ・βから条件の効き方を読み取れること——この2点が、コンテキストを扱うモデル設計でFiLMが選ばれ続ける理由です。

FiLMは「条件をネットワークの計算に深く介入させる」発想の代表格です。次に、コンテキストが時間とともに変わる系列(操作イベントの履歴など)を扱いたくなったら、各位置で参照先を動的に変えられるクロスアテンションが視野に入ります。FiLMはその手前の、最も費用対効果の高い条件付けとして、まず試す価値のある手法です。ここまでで「連結 → FiLM → クロスアテンション」という条件付けの技術が一通り揃います。

次のステップとして、以下もあわせて読むと理解が立体的になります。