深層学習の代表的なモデル図を TikZ で描く

はじめに

論文や資料で使う深層学習モデルの図 ── 全結合層のニューロン、畳み込みの特徴マップ、Transformer のサブ層構成 ── は、TikZ で描けます。ノードや矩形を並べて矢印でつなぐ、というこれまでのグラフィカルモデル・可換図式・ブロック線図の応用です。

要点は 2 つです。

  • 繰り返しは \foreach:同じ形のニューロンやセルをループで配置する
  • 層は座標で管理:x 座標を層ごとに決め、positioning の right=of / above=of で積む

多層パーセプトロン(MLP)

ニューロンを層ごとに \foreach で並べ、層間の全結合を二重ループで引きます。

\begin{tikzpicture}[
  >=Stealth, font=\small,
  neuron/.style={circle, draw, thick, minimum size=7mm, inner sep=0pt},
]
  \foreach \i in {1,2,3}
    \node[neuron] (I\i) at (0, {2-\i}) {};
  \foreach \i in {1,2,3,4}
    \node[neuron, fill=black!6] (H\i) at (2, {2.5-\i}) {};
  \foreach \i in {1,2}
    \node[neuron] (O\i) at (4, {1.5-\i}) {};

  \foreach \i in {1,2,3}
    \foreach \j in {1,2,3,4}
      \draw[->, black!35] (I\i) -- (H\j);
  \foreach \i in {1,2,3,4}
    \foreach \j in {1,2}
      \draw[->, black!35] (H\i) -- (O\j);

  \node[above=6mm of I1] {Input};
  \node[above=6mm of H1] {Hidden};
  \node[above=6mm of O1] {Output};
\end{tikzpicture}
3 ノードの入力層、4 ノードの隠れ層、2 ノードの出力層が全結合された多層パーセプトロン

出力例:多層パーセプトロン。各層の $y$ 座標を {2-\\i} のように式で与え、層間を二重 \\foreach で全結合する。

  • at (0, {2-\i}) の {...} は座標の中で式を評価させる書き方。ノード数に応じて中央そろえになるよう定数を決める
  • エッジは薄いグレー(black!35)にすると、ノードが埋もれない

畳み込みニューラルネットワーク(CNN)

各ステージを、高さの違う矩形で表します。空間サイズが縮み、チャンネルが増える様子を高さで示し、寸法はラベルに書きます。

\begin{tikzpicture}[
  >=Stealth, font=\footnotesize,
  stage/.style={draw, thick, fill=black!5, minimum width=7mm},
  lbl/.style={align=center, font=\scriptsize, anchor=north},
]
  \node[stage, minimum height=26mm] (x) {};
  \node[stage, minimum height=22mm, right=9mm of x]  (c1) {};
  \node[stage, minimum height=14mm, right=9mm of c1] (p1) {};
  \node[stage, minimum height=11mm, right=9mm of p1] (c2) {};
  \node[stage, minimum height=7mm,  right=9mm of c2] (p2) {};
  \node[draw, thick, fill=black!5, minimum width=4mm, minimum height=18mm,
        right=9mm of p2] (fc) {};
  \node[draw, thick, fill=black!5, minimum width=4mm, minimum height=9mm,
        right=8mm of fc] (out) {};

  \foreach \a/\b in {x/c1, c1/p1, p1/c2, c2/p2, p2/fc, fc/out}
    \draw[->] (\a) -- (\b);

  \coordinate (base) at (0,-1.75);
  \node[lbl] at (x  |- base) {Input\\$28\times28$};
  \node[lbl] at (c1 |- base) {Conv\\$24\times24\times8$};
  \node[lbl] at (p1 |- base) {Pool\\$12\times12\times8$};
  % … 以下同様 …
\end{tikzpicture}
入力から畳み込み・プーリングを繰り返し、全結合層を経て 10 クラスのソフトマックスに至る CNN。ステージが進むほど矩形の高さが小さくなる

出力例:CNN。ラベルは (ノード |- base) で共通の $y$ 座標にそろえると、高さの違う矩形でも下端が乱れない。

(<ノード> |- <座標>) は「そのノードの $x$、指定座標の $y$」を表す記法です。ラベルをこの $y$ にそろえると、ブロックの高さがばらばらでも一列に並びます。

展開した RNN

再帰セルを時刻方向に \foreach で並べ、隠れ状態を横に、入力と出力を縦につなぎます。

\begin{tikzpicture}[>=Stealth, font=\small,
  cell/.style={draw, thick, rounded corners, minimum size=10mm}]
  \foreach \t in {1,2,3}
    \node[cell] (A\t) at ({(\t-1)*2.4}, 0) {$A$};

  \coordinate[left=1.1cm of A1] (h0);
  \draw[->] (h0) -- node[above]{$h_0$} (A1);
  \draw[->] (A1) -- node[above]{$h_1$} (A2);
  \draw[->] (A2) -- node[above]{$h_2$} (A3);
  \draw[->] (A3) -- node[above]{$h_3$} ++(1.4,0);

  \foreach \t in {1,2,3}{
    \draw[->] ($(A\t)+(0,-1.1)$) node[below]{$x_{\t}$} -- (A\t);
    \draw[->] (A\t) -- ($(A\t)+(0,1.1)$) node[above]{$y_{\t}$};
  }
\end{tikzpicture}
同じセル A が 3 つ横に並び、隠れ状態 h が左から右へ流れ、各時刻で入力 x が下から、出力 y が上へ出る、展開した RNN

出力例:時刻方向に展開した RNN。セルの $x$ 座標を {(\\t-1)*2.4} で等間隔に置き、$h$・$x_t$・$y_t$ を引く。

Transformer

エンコーダブロック

サブ層(Multi-Head Attention、Feed Forward)と「Add & Norm」を縦に積み、残差結合をサブ層の脇に引き回します。

\begin{tikzpicture}[
  >=Stealth, font=\small, node distance=6mm,
  blk/.style={draw, thick, rounded corners, minimum width=42mm, minimum height=8mm, align=center},
  an/.style ={draw, thick, fill=black!5, minimum width=42mm, minimum height=6mm, align=center},
]
  \coordinate (in);
  \node[blk, above=8mm of in] (mha) {Multi-Head Attention};
  \node[an,  above=of mha]     (an1) {Add \& Norm};
  \node[blk, above=8mm of an1] (ff)  {Feed Forward};
  \node[an,  above=of ff]      (an2) {Add \& Norm};
  \coordinate[above=8mm of an2] (out);

  \draw[->] (in) -- (mha);   \draw[->] (mha) -- (an1);
  \draw[->] (an1) -- (ff);   \draw[->] (ff) -- (an2);
  \draw[->] (an2) -- (out);

  % 残差結合:中点で分岐 → 右へ → Add & Norm の東側へ
  \draw[->] ($(in)!0.5!(mha)$) -- ++(3.1,0) |- (an1.east);
  \draw[->] ($(an1)!0.5!(ff)$) -- ++(3.1,0) |- (an2.east);
\end{tikzpicture}
入力埋め込みから Multi-Head Attention、Add and Norm、Feed Forward、Add and Norm と縦に進み、各サブ層を迂回する残差結合が右側に描かれた Transformer エンコーダブロック

出力例:Transformer エンコーダブロック。$(a)!0.5!(b)$ で線分の中点を取り、そこから残差結合を分岐させて Add \\& Norm の東側へ入れる。

($(a)!0.5!(b)$) は calc の記法で「$a$ と $b$ の中点」。ここから分岐して -- ++(3.1,0) |- (an1.east) で「右へ 3.1、そこから直角に Add & Norm へ」と引き回します。

Multi-Head Attention の内部

MHA そのものを開くと、$V$・$K$・$Q$ をヘッドごとに Linear へ通し、Scaled Dot-Product Attention にかけ、結果を Concat して最後に Linear でまとめる構造です。ヘッドの繰り返しは、Linear〜Attention を破線の枠で囲んで「$h$ 個」と注記して表します。

\node (V) at (0,0) {$V$};  \node (K) at (1.7,0) {$K$};  \node (Q) at (3.4,0) {$Q$};
\node[lin, above=5mm of V] (lV) {Linear};
\node[lin, above=5mm of K] (lK) {Linear};
\node[lin, above=5mm of Q] (lQ) {Linear};
\node[op, above=9mm of lK]        (sdpa)   {Scaled Dot-Product\\Attention};
\node[op, above=7mm of sdpa, minimum width=46mm] (concat) {Concat};
\node[lin, above=6mm of concat, minimum width=22mm] (out) {Linear};

\draw[->] (V) -- (lV);  \draw[->] (K) -- (lK);  \draw[->] (Q) -- (lQ);
\draw[->] (lV) -- (sdpa); \draw[->] (lK) -- (sdpa); \draw[->] (lQ) -- (sdpa);
\draw[->] (sdpa) -- (concat);  \draw[->] (concat) -- (out);

\node[draw, dashed, fit=(lV)(lQ)(sdpa), inner sep=2.5mm,
      label={right:{$h$ heads}}] {};
V・K・Q をそれぞれ Linear に通し、Scaled Dot-Product Attention、Concat、Linear と進む Multi-Head Attention の内部。Linear から Attention までがヘッド数ぶん繰り返されることを破線の枠で示す

出力例:Multi-Head Attention の内部。\\node[fit=...] の破線枠でヘッドの繰り返しを囲み、label で「$h$ heads」と添える。

エンコーダ・デコーダ全体

ブロックを縦に積み、繰り返し数を破線枠の label で $N\times$ と示します。エンコーダの出力をデコーダの Cross Attention に渡す線は、-| で直角に引き回します。

\begin{tikzpicture}[
  >=Stealth, font=\scriptsize, node distance=4.5mm,
  blk/.style={draw, thick, rounded corners, minimum width=26mm, minimum height=5.5mm, align=center},
  an/.style ={draw, thick, fill=black!5, minimum width=26mm, minimum height=4.5mm, align=center},
  emb/.style={draw, thick, minimum width=26mm, minimum height=5mm, align=center},
]
  % ===== エンコーダ =====
  \coordinate (ein);
  \node[emb, above=6mm of ein] (eemb) {Input Embedding\\+ Positional};
  \node[blk, above=7mm of eemb] (emha) {Multi-Head Attention};
  \node[an,  above=of emha]     (ean1) {Add \& Norm};
  \node[blk, above=5mm of ean1] (eff)  {Feed Forward};
  \node[an,  above=of eff]      (ean2) {Add \& Norm};
  \draw[->] (ein) -- (eemb); \draw[->] (eemb) -- (emha);
  \draw[->] (emha) -- (ean1); \draw[->] (ean1) -- (eff); \draw[->] (eff) -- (ean2);
  \draw[->] ($(eemb)!0.5!(emha)$) -- ++(-2.1,0) |- (ean1.west);
  \draw[->] ($(ean1)!0.5!(eff)$)  -- ++(-2.1,0) |- (ean2.west);
  \node[draw, dashed, thick, fit=(emha)(ean2), inner sep=2.6mm,
        label={left:{$N\times$}}] {};
  \coordinate[above=6mm of ean2] (eout);
  \draw[->] (ean2) -- (eout);

  % ===== デコーダ(din を右にずらして同じ要領で)=====
  \coordinate (din) at (6.6,0);
  \node[emb, above=6mm of din] (demb) {Output Embedding\\+ Positional};
  \node[blk, above=7mm of demb] (dmha) {Masked Multi-Head Attn};
  \node[an,  above=of dmha]     (dan1) {Add \& Norm};
  \node[blk, above=5mm of dan1] (dca)  {Cross Attention};
  \node[an,  above=of dca]      (dan2) {Add \& Norm};
  \node[blk, above=5mm of dan2] (dff)  {Feed Forward};
  \node[an,  above=of dff]      (dan3) {Add \& Norm};
  \draw[->] (din) -- (demb); \draw[->] (demb) -- (dmha);
  \draw[->] (dmha) -- (dan1); \draw[->] (dan1) -- (dca);
  \draw[->] (dca) -- (dan2); \draw[->] (dan2) -- (dff); \draw[->] (dff) -- (dan3);
  \draw[->] ($(demb)!0.5!(dmha)$) -- ++(2.1,0) |- (dan1.east);
  \draw[->] ($(dan1)!0.5!(dca)$)  -- ++(2.1,0) |- (dan2.east);
  \draw[->] ($(dan2)!0.5!(dff)$)  -- ++(2.1,0) |- (dan3.east);
  \node[draw, dashed, thick, fit=(dmha)(dan3), inner sep=2.6mm,
        label={right:{$N\times$}}] {};
  \node[emb, above=6mm of dan3] (dlin) {Linear + Softmax};
  \draw[->] (dan3) -- (dlin); \draw[->] (dlin) -- ++(0,5mm);

  % エンコーダ出力 → Cross Attention(K,V)
  \draw[->] (eout) -- ++(0,4mm) -| ([xshift=-13mm]dca.west) -- (dca.west);
\end{tikzpicture}
左にエンコーダ(Input Embedding、Multi-Head Attention、Add and Norm、Feed Forward、Add and Norm)、右にデコーダ(Output Embedding、Masked Multi-Head Attention、Cross Attention、Feed Forward、各 Add and Norm、Linear と Softmax)を配し、エンコーダ出力がデコーダの Cross Attention に入る Transformer 全体図

出力例:Transformer(エンコーダ・デコーダ)。各スタックを破線枠 + label の $N\\times$ で表し、エンコーダ出力を Cross Attention へ -| で結線する。

同じ部品を積む図なので、1 ブロックを \newcommand やスコープにまとめておき、エンコーダ・デコーダで使い回すとコードが短くなります。

さらに高度なモデル

より新しい・複雑なモデルも、これまでの部品(矩形ブロック、\foreach、calc の中点、fit の破線枠、直角配線)の組み合わせで描けます。

残差ブロック(ResNet)

weight → 活性化 → weight の本流と、入力をそのまま足し込む恒等写像(identity)の分岐からなります。分岐は中点から取り、+ の加算ノードへ戻します。

\coordinate (in);
\node[op, above=8mm of in] (w1) {$3\times3$ Conv};
\node[op, above=of w1] (r1) {ReLU};
\node[op, above=of r1] (w2) {$3\times3$ Conv};
\node[circle, draw, inner sep=1.5pt, above=8mm of w2] (add) {$+$};
\node[op, above=8mm of add] (r2) {ReLU};

\draw[->] (in) -- (w1); \draw[->] (w1) -- (r1); \draw[->] (r1) -- (w2);
\draw[->] (w2) -- (add); \draw[->] (add) -- (r2); \draw[->] (r2) -- ++(0,8mm);
\draw[->] ($(in)!0.5!(w1)$) -- ++(3.3,0)
      node[pos=0.72, above]{identity} |- (add.east);
3×3 Conv、ReLU、3×3 Conv と進む本流と、入力からの恒等写像の分岐が加算ノードで合流し、その後 ReLU を通る残差ブロック

出力例:残差ブロック。恒等写像の分岐を中点から取り、加算ノード $+$ の東側へ |- で戻す。

U-Net

高さの違う矩形を左に下降・右に上昇で並べて砂時計形にし、対応する深さのブロックどうしをスキップ接続(破線の弧)で結びます。

\node[b, minimum height=22mm] (e1) at (0,0)     {};
\node[b, minimum height=15mm] (e2) at (1.3,-0.9){};
\node[b, minimum height=9mm]  (e3) at (2.6,-1.7){};
\node[b, minimum height=6mm]  (bn) at (3.9,-2.3){};
\node[b, minimum height=9mm]  (d3) at (5.2,-1.7){};
\node[b, minimum height=15mm] (d2) at (6.5,-0.9){};
\node[b, minimum height=22mm] (d1) at (7.8,0)   {};

\draw[->] (e1) -- (e2); \draw[->] (e2) -- (e3); \draw[->] (e3) -- (bn);
\draw[->] (bn) -- (d3); \draw[->] (d3) -- (d2); \draw[->] (d2) -- (d1);
\draw[->, dashed] (e1.north) to[bend left=18] (d1.north);
\draw[->, dashed] (e2.north) to[bend left=18] (d2.north);
\draw[->, dashed] (e3.north) to[bend left=18] (d3.north);
左から右へ、高さが減っていく3つのエンコーダブロック、最小のボトルネック、高さが戻っていく3つのデコーダブロックが砂時計形に並び、同じ深さのエンコーダとデコーダが破線の弧のスキップ接続で結ばれた U-Net

出力例:U-Net。矩形の $y$ をずらして砂時計形にし、to[bend left] の破線でスキップ接続を弧にする。

拡散モデル

前向き過程 $q$(データ→ノイズ)と逆向き過程 $p_\theta$(ノイズ→データ)を、状態 $x_0,\dots,x_T$ の間に上下 2 本の曲線矢印で描きます。逆向きの 1 ステップを担うのが U-Net などのデノイザです。

\node[xn] (X0) at (0,0)   {$x_0$};
\node[xn] (X1) at (2.9,0) {$x_1$};
\node     (Xd) at (5.8,0) {$\cdots$};
\node[xn] (XT) at (8.7,0) {$x_T$};

\foreach \a/\b in {X0/X1, X1/Xd, Xd/XT}{
  \draw[->] (\a) to[bend left=40] node[above]{$q(x_t\mid x_{t-1})$} (\b);
  \draw[->] (\b) to[bend left=40] node[below]{$p_\theta(x_{t-1}\mid x_t)$} (\a);
}

(x は TikZ 予約キーなのでスタイル名には使えません。xn などにします。)

状態 x0、x1、…、xT が横に並び、上向きの弧が前向き過程 q(データからノイズへ)、下向きの弧が逆向き過程 p_theta(ノイズからデータへ)を表す拡散モデルのマルコフ連鎖

出力例:拡散モデル。to[bend left] で往復の弧を作り、前向き $q$ を上、逆向き $p_\theta$ を下に添える。

GAN

生成器 $G$ と識別器 $D$ を横に並べ、生成サンプル $\hat{x}$ と実データ $x$ の両方を $D$ に入れます。学習信号は破線で $D$ から $G$ へ戻します。

\node (z) at (0,0) {$z$};
\node[b, right=9mm of z] (G) {Generator\\$G$};
\node[b, right=30mm of G] (D) {Discriminator\\$D$};
\node[right=10mm of D] (out) {real / fake};
\coordinate (mid) at ($(G.east)!0.5!(D.west)$);
\node[above=12mm of mid] (xr) {$x$ (real)};

\draw[->] (z) -- (G);
\draw[->] (G) -- node[above]{$\hat{x}$} (D);
\draw[->] (xr) -| (D.north);
\draw[->] (D) -- (out);
\draw[->, dashed] (D.south) -- ++(0,-8mm) -| (G.south) node[pos=0.25, below]{gradient};
ノイズ z から生成器 G が生成サンプル x-hat を作り、実データ x とともに識別器 D に入り real / fake を出力する。D から G へ破線の勾配フィードバックが戻る GAN

出力例:GAN。実データを -| で $D$ の上から入れ、学習信号を破線で $D$ から $G$ へ戻す。

VAE

エンコーダが $\mu,\sigma$ を出し、$\varepsilon$ を下から受けてサンプリングノード $z$ を作り、デコーダへ渡します。再パラメータ化の式はノードの上に添えます。

\node (x) at (0,0) {$x$};
\node[b, right=8mm of x] (E) {Encoder};
\node[right=9mm of E] (par) {$\mu,\ \sigma$};
\node[circle, draw, thick, inner sep=1.5pt, right=10mm of par] (z) {$z$};
\node[b, right=10mm of z] (D) {Decoder};
\node[right=8mm of D] (xh) {$\hat{x}$};
\node[below=10mm of z] (eps) {$\varepsilon \sim \mathcal{N}(0, I)$};

\draw[->] (x) -- (E);   \draw[->] (E) -- (par);
\draw[->] (par) -- (z); \draw[->] (z) -- (D);  \draw[->] (D) -- (xh);
\draw[->] (eps) -- (z);
\node[above=5mm of z] {$z = \mu + \sigma \odot \varepsilon$};
x がエンコーダに入り mu と sigma を出力、下から来る epsilon とあわせてサンプリングノード z を作り、デコーダを経て x-hat を出力する VAE。z の上に再パラメータ化の式が添えられている

出力例:VAE。サンプリングを円ノードで表し、$\varepsilon$ を下から入れて再パラメータ化の式を上に添える。

Vision Transformer(ViT)

画像を格子で区切ってパッチを表し、線形射影・位置埋め込みを経て Transformer エンコーダと MLP ヘッドへ渡します。パッチの格子は矩形ノードに \draw で線を重ねて作ります。

\node[draw, thick, minimum width=13mm, minimum height=13mm] (img) at (0,0) {};
\foreach \k in {1,2} {
  \draw ([xshift=\k*13mm/3]img.south west) -- ([xshift=\k*13mm/3]img.north west);
  \draw ([yshift=\k*13mm/3]img.south west) -- ([yshift=\k*13mm/3]img.south east);
}
\node[below=1mm of img] {patches};
\node[b, right=10mm of img] (emb)  {Linear\\Projection};
\node[b, right=8mm of emb]  (enc)  {Transformer\\Encoder};
\node[b, right=8mm of enc]  (head) {MLP Head};
\node[right=8mm of head] (cls) {class};

\draw[->] (img) -- (emb);
\draw[->] (emb) -- node[above]{$+$ pos.} (enc);
\draw[->] (enc) -- (head);
\draw[->] (head) -- (cls);
3×3 の格子で区切られた画像パッチが線形射影に入り、位置埋め込みを加えて Transformer エンコーダ、MLP ヘッドを経てクラスを出力する Vision Transformer

出力例:Vision Transformer。パッチの格子は矩形ノードの上に \\draw で分割線を重ねて表す。

Mixture of Experts

ルータから複数のエキスパートへ枝分かれし、出力を加算ノードでまとめます。エキスパートは \foreach に evaluate を付けて等間隔に配置します。

\node (in) at (0,-0.7) {$x$};
\node[b, right=9mm of in] (r) {Router};
\foreach \i [evaluate=\i as \y using {2.8-1.4*\i}] in {1,2,3,4}
  \node[b] (E\i) at (4.2,\y) {Expert \i};
\node[circle, draw, thick, inner sep=1.5pt] (sum) at (7.4,-0.7) {$\Sigma$};
\node[right=9mm of sum] (out) {$y$};

\draw[->] (in) -- (r);
\foreach \i in {1,2,3,4} {
  \draw[->] (r) -- (E\i);
  \draw[->] (E\i) -- (sum);
}
\draw[->] (sum) -- (out);
\node[below=6mm of r] {top-$k$ gating};
入力 x がルータに入り、4 つのエキスパートへ枝分かれし、各出力が加算ノード Sigma でまとめられて y を出力する Mixture of Experts。ルータの下に top-k gating と注記

出力例:Mixture of Experts。\\foreach の evaluate でエキスパートの $y$ 座標を等間隔にし、出力を $\Sigma$ に集める。

これらに共通するのは「ブロックを置く → 直角・曲線で配線する → 繰り返しは枠で囲む」という手順で、部品はここまでと同じです。

描き分けのコツ

  • ニューロン単位で見せる図(MLP・小さな RNN セル)は \foreach + 座標式
  • アーキテクチャの流れを見せる図(CNN・Transformer・U-Net)は矩形ブロック + positioning
  • 繰り返し構造(Transformer の $N\times$、MHA の $h$ ヘッド)は fit の破線枠 + label で示す
  • 分岐・合流(残差結合、スキップ接続、拡散の往復)は calc の中点 + -| / to[bend]
  • 和文ラベルを図に入れない:standalone を pdflatex / latex で組むと日本語が通らない。ラベルは英語や数式にとどめ、説明は本文とキャプションに回す(和文を図に入れるなら LuaLaTeX+luatexja が必要)
  • 専用パッケージなら neuralnetwork(LaTeX)や PlotNeuralNet(Python で .tex を生成)もある

運用

図は standalone で 1 枚ずつ別ファイルにし、本文から \input します。ビルドは latexmk、環境の固定は TeX Live 入りコンテナの記事にまとめてあります。この記事の図も、上のコードを standalone でコンパイルして dvisvgm で SVG 化したものです。

関連記事

まとめ

  • 繰り返しは \foreach、層は座標で管理し positioning で積む。
  • MLP はニューロン+二重ループ、CNN は高さの違う矩形、RNN は時刻方向の展開。
  • Transformer はサブ層+残差結合、$N\times$ と $h$ ヘッドは fit の破線枠で表す。
  • ResNet の恒等写像、U-Net のスキップ接続、拡散の往復は、中点分岐と -| / to[bend] で描く。
  • 図に和文を入れないのが無難。凝るなら neuralnetwork や PlotNeuralNet も選択肢。

矩形とループ、座標計算という道具立ては、TikZ の他の記事と共通です。GAN・VAE・ViT・MoE なども同じ部品で組めます。

LaTeX組版・教材校正のご依頼・ご相談はコレクトコネクトへお問い合わせください。

← コラム一覧へ