はじめに
オライリーのロングセラー『ゼロから作るDeep Learning』(斎藤康毅 著)を通読したので、各章の要点を自分なりにまとめます。
本書のコンセプトは「ゼロから作る」ことです。TensorFlowやPyTorchといった外部のディープラーニングフレームワークには頼らず、NumPyだけを武器にニューラルネットワークをスクラッチ実装します。著者が言うように「車の乗り方ではなく車の原理を理解する本」であり、コードを通じて数式の意味が腑に落ちるという体験を目指して設計されています。
1章 Python入門
Pythonの基本文法に加え、NumPyとMatplotlibの使い方を押さえる章です。既存のPythonユーザーは読み飛ばしても問題ありませんが、本書でよく使うブロードキャスト・スライシング・多次元配列操作は後の実装と直結するため、一読を推奨します。
特にブロードキャストは、(2,) の配列と (2,3) の行列間演算など、形状が異なる配列どうしの演算をNumPyが自動で処理してくれる機能です。3章以降のニューラルネットワーク実装ではバイアスの加算など随所で登場します。
2章 パーセプトロン
ニューラルネットワークの起源であるパーセプトロン(1957年、Rosenblatt考案)を解説します。
パーセプトロンは入力信号の重み付き和が閾値を超えると1を出力し、そうでなければ0を出力します。ANDゲートやORゲートはこの単層構造で表現できますが、XORゲートは単層では表現できません。XORが単層で不可能な理由は、単層パーセプトロンが表現できる分離境界が1本の直線に限定されるからです(線形分離不可能)。
この限界を「多層化」によって突破できる点が重要です。NAND・ORの出力をANDへ渡す2層構造でXORを実現でき、さらに多層パーセプトロンは理論上コンピュータと等価な表現力を持ちます——これが「NANDからコンピュータへ」という本章の締め方です。
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
3章 ニューラルネットワーク
パーセプトロンとの本質的な違いは活性化関数にあります。パーセプトロンはステップ関数(0か1かの不連続な切り替え)を使いますが、ニューラルネットワークはシグモイド関数やReLUのような滑らかな非線形関数を使います。
なぜ活性化関数に非線形関数が必要か? 線形関数を活性化関数に使うと、層をいくら深くしても「隠れ層のないネットワーク」と等価になってしまいます。h(x) = cx を3層重ねても y = ax (a = c³)に還元されてしまうのです。
シグモイド関数とReLU
シグモイド関数は 1 / (1 + exp(-x)) で表され、0〜1の連続値を出力します。一方、ReLUは0を超えた入力をそのまま出力し、0以下は0を返します。現在はReLUが主流です。
NumPy行列積による効率的な実装
多次元配列の積(np.dot())を使えば、層から層への信号伝播を数行で実装できます。
A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)
A2 = np.dot(Z1, W2) + B2
Y = identity_function(A2)
本章の最後では学習済みパラメータを読み込み、MNISTの推論処理(93%程度の精度)を実装します。バッチ処理(複数画像をまとめて処理)によって推論が高速化できる点も示されます。
4章 ニューラルネットワークの学習
「学習」とは、損失関数を最小化するよう重みパラメータを自動で更新することです。
損失関数
2乗和誤差と交差エントロピー誤差が紹介されます。分類問題では交差エントロピー E = -Σ(tk * log(yk)) がよく使われます。正解ラベルに対応する出力が大きいほど値が0に近づきます。
なぜ「認識精度」ではなく損失関数を指標にするのか? 認識精度をパラメータ更新の指標にすると、微小なパラメータ変化に対して認識精度がほとんど反応せず、微分値が至るところで0になってしまいます。損失関数は連続的に変化するため、パラメータをどの方向に動かせばよいかの情報(勾配)が得られます。
ミニバッチ学習
MNISTの訓練データ60,000枚に毎回すべてで損失を計算するのは現実的ではないため、ランダムに100枚程度を選んで(ミニバッチ)損失を計算し、それを全体の近似とします。
確率的勾配降下法(SGD)の4ステップ
- ミニバッチをランダムに選択
- 数値微分で各パラメータの勾配を計算
- 勾配方向にパラメータを微小更新
- 1〜3を繰り返す
数値微分は中心差分 (f(x+h) - f(x-h)) / 2h によって実装しますが、計算コストが高い欠点があります。次章で逆伝播法による高速な勾配計算が登場します。
5章 誤差逆伝播法
数値微分では計算に多くの時間を要するという問題を、計算グラフと連鎖律を使って解決します。
計算グラフと局所的な計算
計算グラフはノードと矢印で計算過程を表現します。各ノードは局所的な計算(加算・乗算など)だけを行い、その結果を次のノードへ伝播します。逆伝播は「上流から伝わった微分値に、ノードの局所的な微分を乗算して下流へ渡す」という操作を繰り返します。
連鎖律:合成関数の微分は、各関数の微分の積で表せます。この性質により、出力から入力に向かって微分値を効率的に伝播できます。
各レイヤの逆伝播
- 加算ノード:上流の値をそのまま下流へ流す
- 乗算ノード:順伝播時の入力信号を入れ替えて下流へ掛けて渡す
- ReLUレイヤ:順伝播で入力が0より大きければ上流をそのまま通過、0以下なら0を出力
- Affineレイヤ:
dX = dY・Wᵀ、dW = Xᵀ・dYという転置行列を使った逆伝播 - Softmax-with-Lossレイヤ:逆伝播の出力が
(y - t)というシンプルな形になる
モジュール化の恩恵
各レイヤを forward() と backward() を持つクラスとして実装し、OrderedDict に格納します。勾配の計算は逆順にレイヤの backward() を呼ぶだけです。数値微分との勾配確認によって実装の正しさも検証できます(誤差は 1e-10 〜 1e-13 程度)。
6章 学習に関するテクニック
ニューラルネットワークの学習を実践で使うための技術集です。
パラメータ更新の最適化手法
SGD は勾配方向に一定距離進むシンプルな手法ですが、損失関数の形状が等方的でない場合にジグザグな非効率な経路をたどります。
-
Momentum:速度の概念を導入し、勾配方向に加速しながらジグザグを軽減。
αv項が摩擦(減速)を表現します。 -
AdaGrad:パラメータごとに学習係数を適応的に調整。これまでの勾配の2乗和
hを蓄積し、よく動いたパラメータほど更新量を小さくします。ただし学習を進めるほど更新量が0に近づく問題があります。 - Adam(2015年):MomentumとAdaGradを組み合わせた手法。バイアス補正も行い、現在もっとも広く使われています。
重みの初期値
すべて0や均一な値では逆伝播で全重みが同じ値に更新され、多数の重みを持つ意味がなくなります(対称性の問題)。
-
Xavierの初期値:前層ノード数を
nとして標準偏差1/√nのガウス分布。シグモイド/tanh系に適しています。 -
Heの初期値:標準偏差
√(2/n)。ReLU系に適しており、深い層でも均一な分布を保ちます。
バッチ正規化(Batch Normalization)
2015年提案の手法で、各ミニバッチのデータを平均0・分散1に正規化するレイヤを活性化関数の前後に挿入します。主なメリットは次の3点です。
- 学習を高速化できる(大きな学習係数を使えるようになる)
- 重みの初期値依存性が低下する
- 過学習を抑制できる
正則化
Weight Decay:損失関数にL2ノルム λ/2 * ||W||² を加算し、大きな重みにペナルティを課します。
Dropout:訓練時にニューロンをランダムに消去し、テスト時はすべてのニューロンを使いつつ、訓練中に消去しなかった割合を乗算して出力します。アンサンブル学習と近い効果があります。
ハイパーパラメータの最適化
テストデータでハイパーパラメータを調整すると過学習が起きるため、訓練データから20%程度を検証データとして分離します。探索は対数スケールでランダムサンプリングし(グリッドサーチより効率的)、認識精度の高いものを観察しながら範囲を絞り込んでいきます。
7章 畳み込みニューラルネットワーク(CNN)
画像認識に不可欠なCNNを解説します。
全結合層の問題点:28×28の画像を784次元ベクトルに平坦化してしまうため、空間的な位置関係が失われます。隣接するピクセルの相関を利用できないのです。
畳み込み演算:フィルター(重み)を入力データ上でスライドさせながら積和演算を行います。フィルターのサイズ・パディング・ストライドで出力サイズを制御できます。出力サイズは (H + 2P - FH) / S + 1 で計算されます。3次元データ(チャンネル×高さ×幅)に対応するにはフィルターもチャンネル分用意し、複数フィルターを使えばチャンネル方向に複数の特徴マップを出力できます。
プーリング層:2×2のMax Poolingのように空間サイズを縮小します。学習パラメータを持たず、チャンネル数も変化しません。入力の微小なズレに対してロバストです。
im2colによる効率実装
畳み込み演算をfor文で実装するとNumPyのパフォーマンスを活かせません。im2col 関数を使って4次元の入力データを2次元行列に展開し、行列積として計算します。これにより全結合層のAffineレイヤとほぼ同じ実装で畳み込みが実現できます。
col = im2col(x, FH, FW, stride, pad)
col_W = W.reshape(FN, -1).T
out = np.dot(col, col_W) + b
CNNの可視化:学習後の1層目フィルターを画像として表示すると、エッジやブロブといった原始的な特徴に反応するフィルターが出来上がっています。層が深くなるほど抽象度が上がり、テクスチャ→物体のパーツ→物体クラスへと段階的に変化します(VGG系の研究から判明)。
代表的なアーキテクチャとして、1998年提案のLeNet(手書き文字認識の元祖)と、2012年のILSVRCで革命的な精度を達成したAlexNetを紹介します。
8章 ディープラーニング
本書の集大成として、VGGを参考にした深いCNNを実装しMNISTで99%超の精度を達成します。
層を深くするモチベーション
- パラメータ数の削減:5×5のフィルター1層(25パラメータ)は、3×3を2層重ねる(18パラメータ)ことで同等の受容野をカバーできます。深くするほどパラメータを減らしながら表現力を維持できます。
- 学習の効率性:各層が担う問題が単純化されます。最初の層はエッジだけ学べばよく、エッジを含む画像は大量にあるため少ないデータで効率よく学習できます。
- 表現力の向上:ReLUなどの活性化関数が層間に挟まれることで非線形性が重なり、複雑なパターンを表現できます。
主要なアーキテクチャ
- VGG(2014年):3×3の小さなフィルターによる畳み込み層を2〜4回連続しプーリングで縮小を繰り返す。シンプルで応用性が高く、現在も多く使われています。
- GoogLeNet(2014年優勝):Inception構造(異なるサイズのフィルターを並列適用し結合)を導入。1×1畳み込みによるチャンネル削減でパラメータを抑えます。
- ResNet(2015年、誤認識率3.5%):スキップ接続(入力を2層先の出力に直接加算)を導入し、勾配消失問題を軽減しながら150層超の深いネットワークを実現します。
高速化
AlexNetの処理時間の95%(GPU使用時)が畳み込み層に集中します。GPUによる並列計算(CPU比で7倍以上高速化)、複数GPU・複数マシンによる分散学習、16ビット半精度浮動小数点数への精度削減が有効な手段として紹介されます。
応用例
- 物体検出(R-CNN系):候補領域を抽出してからCNNでクラス分類
- セグメンテーション(FCN):全結合層を畳み込み層で代替し、ピクセル単位のクラス分類を1回のforwardで実現
- 画像キャプション生成(NIC):CNNで特徴抽出→RNNでテキスト逐次生成のマルチモーダル処理
- DCGAN:GeneratorとDiscriminatorが競い合いながら、本物と見紛うほどの画像を生成
- Deep Q-Network(DQN):テレビゲームの画面をそのまま入力としてQ値を近似し、ゲームプレイを学習
総評
数式とコードを両輪で進める構成が本書の強みです。「なぜ活性化関数に非線形が必要か」「なぜ損失関数に精度ではなく交差エントロピーを使うのか」「逆伝播はなぜ動くのか」といった「なぜ」に丁寧に答えてくれます。NumPyレベルの実装から始めるため計算の流れが透けて見え、フレームワークを使う際にもブラックボックス感が薄れます。
翻訳ではなく日本語で書かれたオリジナルの技術書であることも読み進めやすさに貢献しています。本書で得た「設計の意図を理解した上でコードを書く」という習慣は、他の技術領域でも活きるはずです。
本書を読んだ後の次ステップとしては、PyTorchやJAXを使った現代的な実装、Transformer系アーキテクチャ、あるいはResNet以降の論文読み込みあたりが自然な流れかと思います。