ニューラルネット 入門
1 ユニットからネットワークを組み上げる。このページの主張はすべて、手で動かせる図として描いてある。1 ユニットが何を計算するか、折り目なしに積んでも無意味な理由、どの折り目を選ぶか、層が 1 回の行列積である理由、幅と深さがそれぞれ何を買うか —— そしてそれらをつなぐ順伝播。
1 ユニットが計算していること
ユニットは数値をいくつか受け取り、1 つの数を返す。作っているのは 3 つの選択 ——向き、ずらし、そして折り目だ。
誰もが引用する 1 行は output = act(W·x + b) だ。そう書くとただの算術で、算術は覚えやすく、思い描きにくい。入力が 2 つあればその全部を絵にできるので、この節はずっと 1 枚の平面の上で進める。入力はその上の点だ。
まず掛け算から。入力はその平面上の点、重みベクトルは向き、そして w·x は入力がその向きにどれだけ届いているかだ —— 入力をどこへでもドラッグして、太い琥珀色の線分を見てほしい:
w·x が測っていないものに注目してほしい。入力の大きさではない。重みの矢印に垂直な向きに動かしても、この数はまったく変わらない。内積とは類似度スコアであり、重みはこのユニットが探しているパターンそのものだ。
スコアだけでは基準点がない。それを与えるのがバイアスだ。z = w·x + b と書くと、z = 0 を満たす入力がまっすぐな境界をなす。バイアスをドラッグして、境界が原点を離れるのを見てほしい:
境界は決して回らない —— ずれるだけで、そのずれ幅はちょうど −b/|w|、紫の矢印が測っている長さだ。 では、青緑のサンプル点は動いていないのに沈黙側に落ちる。バイアスがなければ、ネットワークのすべての境界は原点を通らされる。
回すのは重みの仕事だ。バイアスはそのままに、重みベクトルを回してみる。境界はつねにそれと直交したまま平面を掃き、サンプルのスコアは 1 回転ごとに 2 度符号を変える:
つまり n 個の重みが n 次元空間の向きを選び、1 個のバイアスがその向きのどこに境界を置くかを選ぶ。 1 ユニットあたり n + 1 個の数、これこそ訓練が学ぶものだ。 1 ユニットが表現できることは、すべてこの 2 つの選択の中にある。
まだ 1 つ足りない。入力を重みの向きに沿って歩かせ、出力を描いてみる —— 活性化前は直線で、整流器はそれを境界のところで 1 回だけ折る:
折れ点を見てほしい。整流ユニットは蝶番だ。片側は平ら、もう片側はまっすぐな坂、そして継ぎ目は境界が横切る場所、t = −0.23 にぴったり載っている。 まで滑らせると、このユニットは何も出力しない。この 1 つの折れ目が非線形性のすべてだ —— 次節は、それがないネットワークがなぜネットワークですらないのかを扱う。
折り目が省けない理由
線形層を 2 枚続けても、線形層 1 枚と同じだ。表現できるものを変えられる部品は、ネットワークの中で整流器だけだ。
あいだに何も挟まず行列を 100 枚積んでも、買えたものは何もない。行列積は結合的なので W₃(W₂(W₁x)) は (W₃W₂W₁)x ——同じ形の行列 1 枚であり、表現力は単層のときと変わらない。
ここに 2 枚ある。入力点の格子が B 層、次に A 層を通る。琥珀色の輪郭は、同じ正方形をその積の行列 1 枚が送る先だ。スライダーで 2 層ぶんを進めてほしい:
格子が毎回きっちり輪郭に着地することに注目してほしい ——も同じだ。 2 枚の 2×2 行列は 8 個の数を使って、4 個の数で書ける写像を作った。もっと深く積んでも同じこと。線形写像の合成は線形写像だ。
その限界は手で確かめられる。XOR はちょうど一方だけが 1 のとき 1 を返してほしい。境界を回し、ずらして、 4 つのラベル付きの点をすべてラベル通りの側へ入れてみてほしい:
3 点。いつでも 3 点だ —— あらゆる角度とオフセットを掃いても 4 には届かず、4 つめの点はつねに外れる。1 ユニットは平面を 2 つに切ることしかできず、対角の 2 隅を残りの 2 隅から分ける切り方は存在しない。この反論がニューラルネットを 1970 年代のあいだ止めた。
整流器の貢献は、それが切断ではなく折りたたみだという点にある。ユニットの境界の沈黙側にあるものはすべて境界の上に押しつぶされる。スライダーで折り目を閉じてほしい:
切り落とされた点の行き先を見てほしい。平面上を滑るのではなく、境界に潰れる。出力が 0 であり、どこから来ようと 0 は同じ 1 つの数だからだ。この層は意図して情報を捨てている —— それが非可逆にし、そして役に立たせている。
XOR には 2 つの折り目で足りる。隠れユニットは 2 つとも同じ向き x₁ + x₂ を見ていて、2 つめが遠い隅ぶんを引き戻す。 1 つずつ取り付けてみよう:
線ではなくスコアを見てほしい。何も入れなくても 4 点中 2 点はただで取れ、 h₁ を入れると 3 点、そして h₂ は出力が引き算するまで 1 つの答えも変えない ——して初めて 4 点になる。ネットワークが 1 と答える領域は2 つの折り目に挟まれた帯で、 1 ユニットでは決して表現できない。
どの折り方を選ぶか
どの非線形性を選ぶかが、勾配がどこまで戻れるか、そして途中で何個のユニットが死ぬかを決める。
実際に使われるのはほぼ 4 つ —— relu、sigmoid、tanh、gelu だ。大事なのは曲線そのものではなく傾きのほうで、逆伝播が掛け算するのは傾きだけだからだ。1 層につき 1 回、戻り道で。
琥珀色の曲線が活性化関数、青緑の曲線がその傾きだ。z に沿ってマーカーを動かし、4 つを切り替えてほしい。読み出しは、いま立っている点での両方の値を出す:
見るのは傾きで、出力ではない。relu はユニットが on のあいだ傾きがちょうど 1 なので、勾配はそのまま通り抜ける。sigmoid は でも 0.25 が上限で、両側へ落ちていく。1 層あたりのこの 4 倍差が、深い積み重ねが訓練できるかを決める。
sigmoid をゼロから外へ、専用の軸で追ってみる。縦軸は対数 —— 1 目盛下がるごとに 10 分の 1 —— で、ローズの破線が傾き 100 分の 1 の位置だ:
いちばん良い位置 z = 0 でも、傾きは 4 分の 1 だ。 10 層積めば勾配は 100 万分の 1 しか残らない。 まで押すと、 1 層だけで 400 分の 1 だ。これが飽和であり、 1990 年代が隠れ層 2 枚で止まっていた理由でもある。
relu の失敗は逆向きで、しかも恒久的だ。沈黙側では傾きがちょうど 0 なので、b がバッチのどの入力よりも下に流れてしまったユニットには、戻るための勾配が残らない:
までドラッグすると、 16 本の棒がすべてローズになる。どの例に対しても出力が 0、だからどの例に対しても勾配が 0、だから二度と更新されない。遅いのではなく死んでいる。 leaky relu と gelu があるのは、傾きがちょうど 0 であることが「ユニットを恒久的に失う」まであと一歩だからだ。
層とは 1 枚の行列だ
ニューロンの袋ではない。1 回の行列積であり、その行列の形が層のコストのすべてだ。
m 個のユニットを横に並べ、どれも同じ n 個の入力を読ませると、長さ n の重みベクトルが n × m の行列に積み上がる。 1 回の積で m 個の活性化前の値がすべて出る —— GPU が層を 1 本の命令列として走らせられるのはこのためだ。
数える前に、まず絵を。行列はどの入力方向も、ある出力方向へ送る ——青緑の入力を単位円に沿ってドラッグし、琥珀色の出力が何になるかをなぞってほしい:
円が楕円にしかならないことに注目してほしい。線形写像は伸ばして回すだけで、それ以上のことはしない。出力の長さは向きによって 0.94 倍から 1.30 倍まで動き、この 2 つの比がこの層の条件数 —— 勾配を歪める倍率だ。
ここから数える。各ユニットは琥珀色の重みを 1 列と、紫のバイアスを 1 個持つ。ユニットを層に足していき、いま組み立てている格子からパラメータ数を読んでほしい:
入力 8 に対してなら 8×12 + 12 = 108 個の数で、どちらの項も効いてくる。重みは n·m で、バイアスは m でしか増えないので、広い入力の上の広い層はほぼ全部が重みだ。幅を倍にすれば、パラメータ数と演算量が同時に倍になる。
バッチ化が変えるのは演算量で、重みではない。B 個の入力を行として積めば、層は 1 回の行列×行列積になる —— ただし内側の次元が一致していれば、だ。W の行数を 128 からずらしてみてほしい:
2 つの内側の辺は同じ尺度で描かれているので、は主張ではなく絵になる。辺が出会わなければ、積は存在しない。バッチ化は演算強度も買う —— バッチ 1 では重み 1 バイトあたり 0.5 演算、バッチ 32 では 16 演算だ。 H100 SXM は素の fp32 で約 67 TFLOP/s、HBM3 は 3.35 TB/s なので、 1 バイトあたりおよそ 20 演算を下回ると、計算ではなくメモリを待っている。
幅と深さ
どちらも直線片を買うが、買える本数が違う。幅はパラメータに線形、深さは指数的だ。
入力 1、出力 1 の relu ネットワークは、重みが何であろうとつねに区分線形関数だ。だから「この構成はどれだけ表現力があるか」に、今回だけは正確な答えがある —— まっすぐな片を何本作れるか、だ。
k ユニットの隠れ層 1 枚が出せるのは、多くても k + 1 本。ユニットを足して、ネットワークが目標曲線に寄っていくのを見てほしい。読み出しは区間全体での最大誤差を出す:
ユニット 1 個がちょうど折れ点 1 つを足す。から にすると、最大誤差は 0.182 から 0.059 —— 層を倍にしておよそ 3 分の 1 で、これが幅の標準的な見返りだ。片は 1 本ずつしか増えない。
深さの買い方は違う。relu ユニット 2 個で区間を半分に折り、次の層が折られたものをもう一度折る。だから片の数は足し算ではなく掛け算になる。層を足してほしい:
2 ユニットの層を —— 合計 10 ユニット、31 パラメータ —— で32 本の直線片が描ける。 1 枚の層で同じ本数を出すには 31 ユニット、94 パラメータが要る。深さは合成し、幅は連結するだけだ。これが Telgarsky の分離定理で、この図はその構成そのものだ。
両方を 1 枚の図に載せる。縦軸は対数で 1 目盛 10 倍、スライダーは同じパラメータ予算を 2 通りに使う —— 全部を幅にか、全部を深さにか:
では、広い層が 21 片、深い積み重ねが 1,024 片に届く。高さではなく形を読んでほしい。対数軸では直線がそのまま指数増加だ。これが言っていないのは、訓練がその片を見つけるかどうか —— これは構成が表現できる上界であって、勾配降下が作るものの保証ではない。
深さには請求書があり、それは戻り道で届く。1 層ごとに勾配へ係数が 1 つ掛かるので、その積はまったく同じ形で指数的になる。層数を安全帯の外へ押してほしい:
1 層あたりのゲインが 0.8 なら、で 1.4×10⁻⁵、100 層で 2×10⁻¹⁰ —— 更新はもう丸め誤差だ。1.2 なら同じ 50 層で 9,100 に達し、最初の大きな一歩で損失が NaN になる。残差接続も正規化層も慎重な初期化も、すべてはこの 1 層あたりのゲインを 1 の近くに保つために存在している。
順伝播
もう発明するものは残っていない。§4 の行列を 3 回、あいだに §2 の折り目、そして1 本のベクトルが毎段で形を変えるだけだ。
4 個の数の入力と 4 → 6 → 4 → 3 のネットワークを用意する。中間の値はすべてベクトルで、それを数字ではなく棒で描くと、本当に効くものが見えてくる ——各層の出力のどれだけが整流器を生き延びるか、だ。
まず第 1 層だけ。上の段が 1 つの入力に対する6 個の活性化前の値、下の段が整流器が残したものだ。入力を 1 方向に掃いてほしい:
消えた棒の数に注目してほしい。初期の入力では 6 ユニット中 3 つがゼロより上、つまりこの層の出力の半分はちょうど 0 だ —— 小さいのではなく、0 だ。relu ネットワークは構造上スパースで、そのスパースさゆえに下の段は上の段より厳密に情報が少ない。
次は全行程だ。7 つの段階、それぞれが行列積 + バイアスか、整流器のどちらか。再生を押すか、スクラバーをドラッグして、ベクトルの長さと形が変わるのを見てほしい:
値ではなく幅を見てほしい。4 個入って、6 個、4 個、そして 3 個出る。各段は 2 種類の演算のどちらかでしかなく、この大きさなら積和 60 回と比較 10 回で答えが全部出る。そしてその中間ベクトルはすべて保持され、その請求はバッチとともに増える。重みのほうは増えない:
実モデルは同じ形に大きな数を入れただけだ。どの講義も最初に出す分類器、784 → h → h → 10 を、重み行列 1 枚につき棒 1 本で描いた。隠れ層の幅をドラッグしてほしい:
パラメータがどこにあるかに注目してほしい。h = 128 でモデルは 118,282 個を持ち、その 85% が第 1 層にある。1 枚 236,032 演算、 fp32 で重み 462 KB。バッチ 128 なら活性値が 525 KB 上乗せされ、重みより重い ——コストが推論より訓練に重くのしかかる理由だ。
出力層と、その 7 行
最後の層が出すのは logits —— 目盛りのない数だ。 softmax がそれを与える。
logits は有界でも正規化済みでもない。[0, 1] の値を出す理由はネットワークのどこにもない。softmax は指数を取って割る。それだけで任意のベクトルが分布になり、順位は変わらない。
1 つめの logit を上へドラッグして、残り 3 つの確率がその分を払うのを見てほしい。次は温度だ —— 指数の前に各 logit を T で割ることが、自信のある答えと平坦な答えを分ける唯一のつまみで、 では最上位クラスがほぼすべてを取る:
確率の総和はつねに 1 で、順序は変わらない。順伝播の全体は 7 行だ:
def forward(x, layers):
for W, b in layers[:-1]:
x = np.maximum(0, x @ W + b)
W, b = layers[-1]
z = x @ W + b
z -= z.max()
return np.exp(z) / np.exp(z).sum()z -= z.max() は飾りではない。softmax は平行移動不変なので引いても答えは変わらず、引かなければ exp(1000) があふれ、ベクトルは NaN で返る。しかも例外は出ない。