第3回では、CNNとRNNを、画像や系列というデータ構造に合わせて読む方法を整理しました。畳み込みでは空間方向に同じカーネルを共有し、RNNでは時間方向に同じ変換を共有する、という見方でした。
第4回では、モデルの構造そのものから一歩進んで、実務でモデルを使うときに避けて通れない技術を見ます。モデルは、精度が高ければそれで終わりではありません。学習に時間がかかりすぎる、GPUメモリに載らない、推論が遅い、端末に載せられない、新しいデータで更新すると古い性能が壊れる、といった問題が必ず出てきます。
ここで扱うのは、次のような問いです。
- 複数GPUで学習するとき、何を平均し、何を待つのか
- 同期SGD、非同期SGD、モデル平均は何が違うのか
- 推論を速くするには、どの計算を近似または削減すればよいのか
- SVD、枝刈り、パラメータ共有、量子化は、それぞれ何を小さくしているのか
- 知識蒸留では、なぜ「正解ラベル」だけでなく先生モデルの確率分布を見るのか
- 転移学習や増分学習で、なぜ古い知識が壊れるのか
- GANは、どの分布をどのように近づけているのか
この回は、理論の名前を覚える回というより、現場でモデルを回すための道具箱を整理する回です。どの技術も万能ではありません。重要なのは、「何を節約して、何を失う可能性があるのか」を見抜くことです。
目次
- 第4回で見るのは「作れるモデル」を「回せるモデル」にする技術
- 複数GPU訓練は「データを分ける」か「モデルを分ける」か
- データ並列では勾配を平均する
- 同期SGD、非同期SGD、モデル平均は同じではない
- モデル並列とパイプライン並列は巨大モデルの置き場所を作る
- 学習そのものを軽くする実務技術:勾配チェックポイント・混合精度・勾配累積
- 推論高速化は「同じ判断を安く近似する」発想
- SVD低ランク近似は大きな行列を2つの小さな行列に分ける
- 枝刈りは重みではなく計算の通り道を消す
- パラメータ共有は重みを辞書の番号で持つ
- 知識蒸留は先生の判断の濃淡を学生へ渡す
- 量子化は実数を低精度の目盛りへ写す
- Binary Netは極端な低精度化で計算を置き換える
- FFTと循環行列は構造を入れて行列積を軽くする
- 転移学習と増分学習は新旧タスクの折り合いを取る
- GANは生成器と判別器を競わせて分布を近づける
- 実務で高度化技術を選ぶためのチェックリスト
- この第4回のまとめ
- 深層学習の一本の糸:4回分を最も簡単な言葉でつなぐ
1. 第4回で見るのは「作れるモデル」を「回せるモデル」にする技術
第1回から第3回までは、モデルの中で何が計算されているかを主に見てきました。ここで一度、3回分の内容を大まかに振り返っておきます。
| 回 | 中心的な問い | 押さえた考え方 |
|---|---|---|
| 第1回 | 数式は実務の判断にどうつながるか | 行列とSVDは「データの形」と「重要な方向」を表し、損失関数は失敗の罰し方の翻訳であり、正則化とバイアス・バリアンスは解の好みと改善の地図になる |
| 第2回 | ネットワークはどう学習が進むのか | 前向き計算はshapeを保ちながら表現を作り、逆伝播は損失への責任を各層へ配り、初期化・BatchNorm・残差接続は勾配の道を整え、正則化は汎化のために自由度を抑える |
| 第3回 | 画像と系列はどう構造を読むのか | CNNは空間方向に、RNNは時間方向に同じ変換を共有することで、少ないパラメータで広い入力に対応する |
この3回で見てきたのは、いわば「1台のGPU、1つのモデル、1回の学習」という理想的な状況を土台にした考え方でした。第4回からは視点を変え、そのモデルを、複数のGPU、限られたメモリ、遅い推論、更新の必要性、生成というより現実的な制約の中で、どう回すかを見ていきます。
しかし実務では、モデルを設計できるだけでは足りません。たとえば、精度の高いモデルを作っても、次のような問題が起こります。
| 問題 | 典型的な対策 |
|---|---|
| 学習が遅い | 複数GPU訓練、データ並列、モデル並列 |
| GPUメモリに載らない | モデル並列、チェックポイント化、低ランク化 |
| 推論が遅い | SVD、枝刈り、量子化、蒸留 |
| モデルが大きすぎる | パラメータ共有、量子化、Binary Net |
| 新しいデータで更新すると古い性能が落ちる | 転移学習、増分学習、蒸留、リプレイ |
| 本物らしいデータを生成したい | GANなどの生成モデル |
これらは、一見すると別々の技術に見えます。しかし共通する考え方があります。どれも、モデルの能力、計算量、メモリ、データ、運用の制約の間で折り合いを取る技術です。
第2回で見た正則化が「学習しすぎないための工夫」だったとすれば、第4回で見る技術は「重すぎるモデルを現場で使える形にするための工夫」です。
まずは、学習を速くするための複数GPU訓練から見ていきます。
2. 複数GPU訓練は「データを分ける」か「モデルを分ける」か
複数GPUを使う目的は単純です。1枚のGPUだけでは、学習に時間がかかりすぎる、またはモデルやbatchがメモリに載らないからです。
大きく分けると、複数GPU訓練には2つの考え方があります。
| 方法 | 分けるもの | 向いている場面 |
|---|---|---|
| データ並列 | batchのサンプル | モデルは1枚のGPUに載るが、データが多く学習が遅い |
| モデル並列 | モデルの層・テンソル・計算 | モデル自体が1枚のGPUに載らない |
データ並列では、各GPUに同じモデルのコピーを置きます。そして、ミニバッチをGPUの数だけ分け、各GPUが別々のデータを処理します。最後に勾配を集めて、全GPUで同じパラメータ更新を行います。
モデル並列では、モデルそのものを分割します。たとえば前半の層をGPU 1、後半の層をGPU 2に置く、巨大な行列を複数GPUに分ける、Transformerの層をパイプラインとして分ける、という方法があります。
直感的には、データ並列は「同じ問題集を複数人で分担して解き、最後に答え合わせをする」方法です。モデル並列は「1つの大きな装置を複数の作業台に分けて置く」方法です。
まず、多くの現場で最初に使うデータ並列から見ます。
3. データ並列では勾配を平均する
GPUが $K$ 台あるとします。各GPUは同じパラメータ $\theta$ を持ち、異なるデータ分割 $B_k$ を処理します。GPU $k$ が計算するローカル損失を
$$
J_k(\theta)=\frac1{|B_k|}\sum_{(x,y)\in B_k}\ell(f_\theta(x),y)
$$
とします。各GPUで勾配を計算すると、
$$
g_k=\nabla_\theta J_k(\theta)
$$
です。各GPUのbatchサイズが同じなら、全体の勾配は平均で
$$
g=\frac1K\sum_{k=1}^K g_k
$$
になります。同期SGDでは、この $g$ を使って全GPUで同じ更新をします。
$$
\theta\leftarrow\theta-\eta g
$$
実務でこの勾配の集約をどう行うかには、大きく2つの実装パターンがあります。1つはパラメータサーバー方式で、専用のサーバーが各GPUから勾配を集めて平均を計算し、更新後のパラメータを配り直します。もう1つはAll-Reduce方式で、専用サーバーを置かず、GPU同士が直接通信し合って全員が同じ平均値を持つまで勾配をやり取りします。PyTorchのDistributedDataParallelやHorovodのような現場で広く使われるツールは、GPUをリング状につないで通信するring All-Reduceに近い方式を採用していることが多く、GPU数が増えても通信量が比較的抑えられるという利点があります。どちらの方式でも最終的にすべてのGPUが同じ平均勾配を得るという結果は同じですが、通信の経路と負荷分散の設計が異なります。
ここで重要なのは、各GPUが別々のモデルを育てているわけではないという点です。各GPUは一時的に別々のデータで勾配を計算しますが、更新後のパラメータは再び同じになります。
小さな例で確認します。GPUが4台あり、それぞれのローカルbatchが32なら、全体batchは128です。各GPUが32サンプル分の勾配を計算し、最後に4つの勾配を平均します。これは、1台のGPUで128サンプルをまとめて処理したときの勾配に近い計算です。
ただし、完全に同じになるには条件があります。BatchNormの統計、乱数、データ順序、数値丸め、勾配の集約方法などによって、小さな違いが出ることがあります。実務では、複数GPUにしたら精度が少し変わること自体は珍しくありません。
データ並列で見るべき実務ポイントは次の通りです。
| 見るもの | 注意点 |
|---|---|
| global batch size | GPU数を増やすと実質batchが大きくなり、学習率の調整が必要になることがある |
| 通信量 | 勾配を全GPUで集めるため、モデルが大きいと通信がボトルネックになる |
| 遅いGPU | 同期方式では、最も遅いworkerを待つことになる |
| BatchNorm | GPUごとに統計を取るか、同期BatchNormを使うかで結果が変わる |
表の1行目にある学習率の調整は、実務でよく見落とされる点なので、もう少し具体的に触れておきます。GPUを $K$ 台使い、各GPUのローカルbatchサイズを $b$ のまま増やすと、global batch sizeは $Kb$ になります。1回の更新に使うサンプル数が増えるということは、同じデータ量に対する更新回数がその分減るということでもあります。これを補うためによく使われるのが、学習率をGPU数、つまりbatchサイズの倍率に比例して大きくする、線形スケーリング則です。たとえばGPU1台・batch 32で学習率0.1が適切だったなら、GPU4台・batch 128では学習率をおよそ0.4まで引き上げる、という考え方です。ただし、学習の最初から大きな学習率をいきなり使うと、パラメータがまだ不安定な段階で発散しやすくなります。そこで、最初の数エポックだけ学習率を小さい値から徐々に引き上げるウォームアップと組み合わせるのが一般的です。GPU数を増やしたのに精度が落ちた、というときは、まずこの学習率とウォームアップの調整を疑う価値があります。
この勾配の集め方を理解すると、同期SGD、非同期SGD、モデル平均の違いも整理しやすくなります。
4. 同期SGD、非同期SGD、モデル平均は同じではない
複数GPU訓練では、「何をいつ合わせるか」が大事です。
同期SGDでは、すべてのGPUが同じタイミングで勾配を出し、それらを平均してから更新します。
$$
g_t=\frac1K\sum_{k=1}^K g_{k,t},\qquad \theta_{t+1}=\theta_t-\eta g_t
$$
この方法では、更新後のパラメータが全GPUで一致します。再現性も比較的高く、学習も安定しやすいです。その代わり、遅いGPUや遅い通信を待つ必要があります。
非同期SGDでは、workerがそれぞれ勾配を計算し、準備できたものからパラメータサーバーなどへ送ります。待ち時間は減りますが、あるworkerが古いパラメータで計算した勾配を後から送ってくることがあります。
これを式で書くと、
$$
\theta_{t+1}=\theta_t-\eta\nabla J(\theta_{t-\tau})
$$
のように、現在の $\theta_t$ ではなく、少し前の $\theta_{t-\tau}$ に基づく勾配で更新していることになります。この遅れた勾配を stale gradient と呼びます。スループットは高くなりやすい一方、学習が不安定になることがあります。
もう1つ、モデル平均という考え方もあります。各GPUや各workerがしばらく独立に学習し、最後にパラメータを平均します。
$$
\theta\leftarrow\frac1K\sum_{k=1}^K\theta_k
$$
1ステップだけ、同じ初期値から同じ学習率でSGDを行うなら、モデル平均と勾配平均は似た形になります。実際、
$$
\theta_k=\theta-\eta g_k
$$
なら、
$$
\frac1K\sum_k\theta_k
=\theta-\eta\frac1K\sum_k g_k
$$
です。しかし、複数ステップを各workerが独立に進むと、話は変わります。ニューラルネットワークの損失面は線形ではないため、各workerが別々の場所へ進んだ後にパラメータを平均しても、勾配平均で1歩ずつ進んだ結果とは一致しません。MomentumやAdamの内部状態、BatchNormの統計も絡むので、単純な平均では期待どおりにならないことがあります。
実務では、まず同期データ並列を標準形として考えます。非同期やローカル更新を増やす方法は、通信削減や巨大クラスタ向けの工夫として使いますが、安定性との交換になります。
データを分けてもモデルが1枚のGPUに載らない場合は、次にモデルそのものを分ける必要があります。
5. モデル並列とパイプライン並列は巨大モデルの置き場所を作る
モデル並列は、モデルの一部を別々のGPUに置く方法です。
代表的には、次のような分け方があります。
| 分け方 | 内容 | 例 |
|---|---|---|
| 層並列 | 層の前半と後半を別GPUに置く | 深いネットワークを層ごとに分ける |
| テンソル並列 | 1つの大きな行列計算を分割する | 巨大な全結合層やAttentionの射影行列 |
| パイプライン並列 | 層を段階に分け、micro-batchを流す | Transformerの層を複数GPUに分ける |
層並列は分かりやすいです。GPU 1で前半の層を計算し、その出力をGPU 2へ送り、GPU 2で後半を計算します。ただし、GPU間で活性値を送る必要があるため、通信が増えます。また、GPU 2が計算している間にGPU 1が待ってしまうと、利用効率が悪くなります。
パイプライン並列では、この待ち時間を減らすために、1つの大きなbatchを小さなmicro-batchに分けて流します。工場の流れ作業に近いです。第1段階がmicro-batch 2を処理している間に、第2段階はmicro-batch 1を処理します。
段数を $P$、micro-batch数を $M$ とすると、単純化した利用効率はおおよそ
$$
\frac{M}{M+P-1}
$$
で見積もれます。$P=4$、$M=1$ なら効率は $1/4$ で、多くの時間が待ちになります。$M=16$ なら、
$$
\frac{16}{16+4-1}=\frac{16}{19}\approx0.84
$$
まで改善します。micro-batchを増やすほど待ち時間は減りますが、メモリや実装の複雑さも増えます。
モデル並列で大事なのは、「GPUを増やしたから自動的に速くなる」と考えないことです。通信量、待ち時間、メモリ、実装の複雑さを含めて設計する必要があります。
ここまでは、GPUを何枚も使って分担する方法を見てきました。次は、GPUの枚数を増やさなくても、1枚のGPUの中で学習を軽く、速くするための実務技術を見ます。
6. 学習そのものを軽くする実務技術:勾配チェックポイント・混合精度・勾配累積
複数GPU訓練は、GPUの「枚数」を増やすことで学習を速く、大きくする方法でした。しかし、GPUを増やせない現場や、そもそも1枚のGPUの中で学習をもっと軽く、速くしたい場面も多くあります。ここでは、そのための3つの実務技術を見ます。いずれも、第2回で見た前向き計算と逆伝播の仕組みを、そのままメモリと速度の工夫に読み替えたものです。
勾配チェックポイント(活性化の再計算)
第2回で見たように、逆伝播では $\Delta_l=(\Delta_{l+1}W_{l+1}^T)\odot f_l'(H_l)$ のように、各層の活性化前の値 $H_l$ を使います。つまり、前向き計算で作ったすべての層の中間結果を、逆伝播が終わるまでメモリに残しておく必要があります。層が深くなるほど、この「取っておく」ためのメモリが積み上がり、GPUメモリを圧迫します。
勾配チェックポイントは、この問題に対して「全部覚えておく」のではなく「一部だけ覚えておいて、残りは必要になったら計算し直す」という発想で答えます。具体的には、いくつかの層ごとに区切り、その区切り目の値だけを保存します。逆伝播で区切りの内側の値が必要になったら、保存しておいた区切り目の値から、その区間だけ前向き計算をもう一度行い、必要な中間結果を作り直します。
これは、メモリと計算時間を交換する取引です。中間結果を保存しない分メモリは節約できますが、同じ区間の前向き計算を1回余分に行うため、学習時間は増えます。実務では、GPUメモリが足りずbatchサイズを小さくせざるを得ない場面で、batchサイズを保ったまま学習できるようにする目的でよく使われます。1節の表で「GPUメモリに載らない」への対策として挙げた「チェックポイント化」は、このことを指しています。
混合精度学習(AMP)
この後で見る量子化は、学習済みモデルを推論用に低精度化する技術です。混合精度学習は、この「低精度で計算する」という発想を、学習そのものに持ち込みます。
具体的には、前向き計算と逆伝播の大部分をFP16やBF16のような16bit精度で行い、パラメータの更新に使うマスターコピーだけはFP32の高精度で持ちます。行列積のような計算量の多い処理を16bitで行うことで、多くのGPUで計算速度が上がり、活性値のメモリも半分近くまで減らせます。
ただし、そのまま16bitにするだけでは問題が起きやすくなります。FP16は表現できる数値の範囲がFP32より狭いため、小さな勾配の値が0に丸められて消えてしまう、アンダーフローが起きることがあります。これに対する代表的な対策が損失スケーリングです。損失に大きな定数を掛けてから逆伝播することで、勾配の値を全体的にFP16が表現しやすい範囲まで持ち上げ、逆伝播が終わった後、パラメータ更新の直前で同じ定数で割り戻します。第2回で見たAdamの分母に加える小さな $\varepsilon$ と同じように、これも数値計算を安定させるための地味だが実務的な工夫です。
勾配累積
第2回で見たミニバッチは、batchサイズを大きくするほど勾配のばらつきが減り、GPUも使いやすくなる一方、メモリを消費するという話でした。GPUメモリの上限で決まる最大batchサイズが、学習として望ましいbatchサイズより小さいことは実務でよくあります。
勾配累積は、この制約に対して、「1回の更新で使うbatchを、複数回に分けて集める」という発想で対応します。たとえば、本当は256サンプルのbatchで1回更新したいが、GPUメモリの都合で64サンプルずつしか前向き・逆向き計算ができないとします。このとき、64サンプルの小さなbatch(micro-batch)を4回処理してそれぞれの勾配を足し合わせ、4回分がたまったところで初めてパラメータを1回更新します。
$$
g=\frac14\sum_{i=1}^{4}g_i,\qquad \theta\leftarrow\theta-\eta g
$$
これは64サンプルずつ4回に分けて処理しているだけですが、パラメータ更新の頻度と平均のとり方は、256サンプルを1回で処理したときとほぼ同じになります。先ほど見たデータ並列が「複数GPUに分けて同時に計算し、最後に勾配を平均する」方法だったのに対し、勾配累積は「1枚のGPUで時間をずらして計算し、最後に勾配を平均する」方法だと考えると、両者のつながりが見えやすくなります。GPUが複数あるなら並列化で、GPUが1枚しかないかメモリが足りないなら勾配累積で、同じ「大きなbatchで学習したい」という要求に応えていることになります。
ここまでの3つの技術は、いずれも学習中に、GPU1枚あたりのメモリと速度を工夫する話でした。次は、学習済みモデルをいかに軽く、速く使うかを見ます。
7. 推論高速化は「同じ判断を安く近似する」発想
推論高速化では、学習済みモデルの出力をなるべく保ちながら、計算量、メモリ、モデルサイズ、消費電力を減らします。
代表的な方法を整理すると、次のようになります。
| 技術 | 何を小さくするか | 失いやすいもの |
|---|---|---|
| SVD低ランク近似 | 大きな行列のランク | 表現力、近似精度 |
| 枝刈り | 不要な通路や重み | 冗長性、場合によっては精度 |
| パラメータ共有 | 重みの種類数 | 細かな重みの違い |
| 知識蒸留 | モデル自体のサイズ | 先生モデルを超える能力 |
| 量子化 | 数値のビット幅 | 数値精度、外れ値への強さ |
| Binary Net | 乗算そのもの | 表現力、学習のしやすさ |
実務では、これらを単独で使うこともあれば、組み合わせることもあります。たとえば、大きなモデルを蒸留して小さなモデルを作り、その小さなモデルをさらにINT8量子化する、という流れです。
ただし、軽量化は「魔法の圧縮」ではありません。どの近似にも、どこかで情報の削減があります。したがって、圧縮前後で精度、レイテンシ、スループット、メモリ、消費電力を実測する必要があります。
まず、行列を低ランクで近似するSVDから見ます。
8. SVD低ランク近似は大きな行列を2つの小さな行列に分ける
全結合層や $1\times1$ 畳み込みでは、大きな行列積が計算の中心になります。重み行列を $W\in\mathbb R^{m\times n}$ とすると、出力は
$$
y=Wx
$$
です。SVDを使うと、$W$ は
$$
W=U\Sigma V^T
$$
と分解できます。特異値の大きい上位 $r$ 個だけを残すと、
$$
W\approx U_r\Sigma_rV_r^T
$$
です。ここで
$$
A=U_r\Sigma_r,\qquad B=V_r^T
$$
と置けば、
$$
W\approx AB
$$
になります。元の1回の大きな線形変換を、
$$
y=Wx\approx A(Bx)
$$
という2回の小さな線形変換に置き換えられます。
パラメータ数は、元の $mn$ から
$$
r(m+n)
$$
に減ります。圧縮が得になる条件は、
$$
r(m+n)<mn
$$
です。つまり、
$$
r<\frac{mn}{m+n}
$$
なら、パラメータ数は減ります。
具体例で見ます。$m=n=4096$ の全結合層なら、元のパラメータ数は
$$
4096\times4096=16,777,216
$$
です。ランク $r=512$ で近似すると、
$$
512(4096+4096)=4,194,304
$$
になります。約4分の1です。
パラメータ数だけでなく、計算量も同じ理屈で減ります。$y=Wx$ をそのまま計算すると、掛け算の回数はおおよそ $mn$ 回、つまり $4096\times4096\approx1677$万回です。ところが $y\approx A(Bx)$ のように2段階に分けると、まず $Bx$ の計算に $rn$ 回、その結果に $A$ を掛けるのに $rm$ 回、合わせて $r(m+n)=512\times8192\approx419$万回で済みます。パラメータを4分の1に減らしたのと同じ比率で、掛け算の回数もおよそ4分の1に減っていることが分かります。SVD低ランク近似が現場で軽くなると言われるのは、覚えておく数が減るだけでなく、実際に掛け算の回数そのものが減るからです。
実務での流れは、次のようになります。
- まず通常のモデルを学習する
- 圧縮したい層の重み $W$ にSVDをかける
- 上位 $r$ 個の成分だけで2つの線形層に置き換える
- 置き換えたモデルを微調整する
注意点は、2つの線形層の間に活性化関数を入れないことです。ここでやりたいのは、元の線形変換 $W$ を $AB$ で近似することです。間にReLUなどを入れると、別のモデルになってしまいます。
SVDは、重み行列に低ランク構造があるときには効きます。一方、重要な情報が多くの特異値に広く分散している場合は、低ランク化すると精度が落ちやすくなります。そのため、ランク $r$ は圧縮率だけで決めず、精度との曲線を見ながら決めます。
次は、重みやチャンネルを削る枝刈りを見ます。
9. 枝刈りは重みではなく計算の通り道を消す
枝刈りは、重要度の低い重み、チャンネル、ニューロン、Attention headなどを削る方法です。
大きく分けると、非構造化枝刈りと構造化枝刈りがあります。
| 方法 | 削るもの | 実際の高速化 |
|---|---|---|
| 非構造化枝刈り | 個々の重み | 専用の疎行列演算がないと速くなりにくい |
| 構造化枝刈り | チャンネル、フィルタ、head、層 | 通常の密な演算のまま小さくでき、速くなりやすい |
たとえば、畳み込み層のパラメータ数は
$$
K_hK_wC_{in}C_{out}
$$
です。$3\times3$ 畳み込みで、$C_{in}=64$、$C_{out}=128$ なら、重みは
$$
3\times3\times64\times128=73,728
$$
個です。出力チャンネルを128から96へ削ると、同じ層は
$$
3\times3\times64\times96=55,296
$$
個になります。さらに重要なのは、次の畳み込み層の入力チャンネルも128から96へ減ることです。つまり、1つのチャンネルを消すと、その層だけでなく後続の計算も軽くなります。
実務での枝刈りの流れは、次のように考えると分かりやすいです。
- 通常のモデルを学習する
- 各チャンネルや重みの重要度を測る
- 重要度の低いものを少し削る
- 精度を回復させるために微調整する
- 必要なら、2から4を繰り返す
重要度の測り方には、重みの絶対値、BatchNormのスケール係数、勾配、出力の活性値、削ったときの損失変化などがあります。単に小さい重みを消せばよいとは限りません。あるチャンネルは重みが小さくても、特定の入力で重要な役割を持っていることがあります。
非構造化枝刈りでは、重み行列の中に0が増えます。しかし、通常のGPUは密な行列演算が得意です。0が多くても、標準の密行列演算を使っている限り、計算時間はあまり減らないことがあります。実際に速くしたいなら、構造化枝刈りや疎演算に対応した実行環境まで含めて考える必要があります。
枝刈りが「通り道を削る」発想だとすれば、次のパラメータ共有は「似た重みを同じ値として持つ」発想です。
10. パラメータ共有は重みを辞書の番号で持つ
パラメータ共有では、多数の重みを少数の代表値で表します。代表値をコードブック、各重みがどの代表値を使うかをインデックスと考えると分かりやすいです。
重み $w_i$ を、コードブック中心 $c_1,\ldots,c_M$ のどれかで近似します。
$$
w_i\approx c_{q(i)}
$$
ここで $q(i)$ は、重み $w_i$ が使うコードブック番号です。
たとえば、100万個の重みを32個の代表値で表すとします。元のFP32重みは、1個あたり32bitなので、重みだけで
$$
1,000,000\times32=32,000,000\ \text{bit}
$$
必要です。32個の代表値なら、インデックスは5bitで表せます。すると、インデックスは
$$
1,000,000\times5=5,000,000\ \text{bit}
$$
です。コードブック本体は
$$
32\times32=1,024\ \text{bit}
$$
しかありません。インデックスとコードブックを合わせても、必要なビット数はおよそ
$$
5,000,000+1,024\approx5,001,024\ \text{bit}
$$
です。元の32,000,000bitと比べると、必要なビット数はおよそ6分の1にまで減ります。32個の代表値を「32色の色鉛筆セット」にたとえると分かりやすくなります。100万個の重みという塗り絵を、32色のどれか1色で塗り分けてしまえば、あとは各マスがどの色番号かという5bitの情報さえ持っておけば、元の絵をほぼ再現できます。色そのもの、つまりコードブックはたった32個しかないので、保存コストのほとんどは番号、つまりインデックスの方にかかっている、という点が実務上のポイントです。
実務での流れは、次のようになります。
- 学習済みの重みを集める
- k-meansなどで代表値を決める
- 各重みを最も近い代表値の番号に置き換える
- 必要なら、コードブック中心を微調整する
この方法はモデルサイズを小さくできますが、推論速度が必ず上がるとは限りません。インデックスから代表値を引く処理が必要になるため、ハードウェアや実装によっては、メモリは減っても計算は速くならないことがあります。
ここまでは、同じモデルを近似して軽くする話でした。次は、大きなモデルの知識を小さなモデルへ移す知識蒸留を見ます。
11. 知識蒸留は先生の判断の濃淡を学生へ渡す
知識蒸留では、大きく高性能な先生モデルを使って、小さな学生モデルを学習します。
普通の教師あり学習では、正解ラベルはone-hotです。たとえば画像が猫なら、猫が1、それ以外が0です。しかし、先生モデルの出力確率には、もっと細かな情報が含まれています。猫の画像に対して、先生が「猫0.82、虎0.10、犬0.04、車0.00」と出すなら、虎は犬より猫に近いという関係も伝わります。
この「クラス間の近さ」が、蒸留で学生が受け取る追加情報です。
先生と学生の確率分布を比べるために、KLダイバージェンスを使います。
$$
D_{KL}(P|Q)=\sum_x P(x)\log\frac{P(x)}{Q(x)}
$$
これは非負ですが、対称ではありません。
$$
D_{KL}(P|Q)\ne D_{KL}(Q|P)
$$
したがって、厳密な意味での距離ではありません。蒸留では、先生分布 $P$ を基準にして、学生分布 $Q$ がそこからどれくらいずれているかを測る、と考えるとよいです。
先生モデルと学生モデルのlogitを $z_t,z_s$ とします。温度 $T$ を使ったsoftmaxは
$$
p_i^{(T)}=\frac{e^{z_i/T}}{\sum_j e^{z_j/T}}
$$
です。$T$ を大きくすると、確率分布はなだらかになります。たとえば、logitが $[5,2,0]$ のとき、$T=1$ では最大クラスに確率が強く集中します。$T=4$ にすると差が薄まり、2番目や3番目のクラスにも確率が残ります。
学生の損失は、正解ラベルに対する通常の損失と、先生の軟らかい分布に対する損失を混ぜます。
$$
L=(1-\alpha)L_{hard}+\alpha T^2D_{KL}(p_t^{(T)}|p_s^{(T)})
$$
$T^2$ を掛けるのは、温度を上げるとsoftmaxの勾配スケールが小さくなりやすいためです。実装では、$\alpha$ と $T$ はかなり重要なハイパーパラメータになります。
通常の交差エントロピーについても確認しておきます。学生のsoftmax出力を $s$、教師分布を $y$ とすると、
$$
L=-\sum_i y_i\log s_i
$$
です。softmaxと交差エントロピーを合わせると、logitに対する勾配は
$$
\frac{\partial L}{\partial z_i}=s_i-y_i
$$
になります。つまり、学生の確率 $s_i$ が教師の確率 $y_i$ より大きければ下げ、小さければ上げる方向に勾配が出ます。蒸留では、この $y_i$ がone-hotではなく、先生モデルのなだらかな分布になります。
実務での蒸留は、次のような場面で効きます。
| 場面 | 使い方 |
|---|---|
| 大モデルをサーバーで学習し、小モデルを端末へ載せる | 先生モデルの出力で小モデルを学習する |
| 複数モデルの集成を1つのモデルにしたい | 集成の平均確率を先生分布にする |
| 増分学習で古い性能を保ちたい | 古いモデルを先生にして、新モデルの出力を縛る |
蒸留は強力ですが、先生が間違っている知識も学生に伝わります。先生モデルのバイアス、古いデータへの偏り、誤分類の癖も一緒に移ることがあります。そのため、蒸留後も必ず本来の評価データで確認します。
次は、数値そのものを低精度にする量子化を見ます。
12. 量子化は実数を低精度の目盛りへ写す
量子化は、FP32のような高精度の実数を、FP16、BF16、INT8などの低精度表現へ変換する技術です。目的は、モデルサイズ、メモリ帯域、計算時間、消費電力を減らすことです。
ここでは、実務でよく出てくるアフィン量子化を見ます。実数 $x$ を量子化された整数 $q$ で表すとき、
$$
q=\operatorname{clip}\left(\operatorname{round}\left(\frac{x}{s}\right)+z\right)
$$
とします。復元は
$$
\hat x=s(q-z)
$$
です。$s$ はscale、$z$ はzero-pointです。$s$ は目盛りの幅、$z$ は実数の0が整数表現でどこに対応するかを表します。
実数範囲 $[a,b]$ を整数範囲 $[q_{min},q_{max}]$ に写すなら、典型的には
$$
s=\frac{b-a}{q_{max}-q_{min}}
$$
とし、
$$
z=\operatorname{round}\left(q_{min}-\frac{a}{s}\right)
$$
のように決めます。
たとえば、実数範囲を $[-1,1]$、符号付きINT8の範囲をおおよそ $[-127,127]$ とすると、対称量子化では
$$
s\approx\frac1{127}
$$
です。$x=0.5$ なら、
$$
q\approx\operatorname{round}(0.5\times127)=64
$$
となり、復元値は
$$
\hat x\approx\frac{64}{127}\approx0.504
$$
です。元の値と完全には一致しません。このずれが量子化誤差です。
行列積でも同じ考え方です。入力と重みを
$$
x\approx s_x(q_x-z_x),\qquad w\approx s_w(q_w-z_w)
$$
とすると、内積は
$$
\sum_i x_iw_i\approx s_xs_w\sum_i(q_{x,i}-z_x)(q_{w,i}-z_w)
$$
になります。中の和は整数で計算でき、最後にスケールを掛けて戻します。これが、INT8推論で高速化や省メモリが得られる基本的な理由です。
量子化には、主に2つの使い方があります。
| 方法 | 流れ | 特徴 |
|---|---|---|
| PTQ | 学習後に量子化し、少量の校正データでscaleを決める | 手軽だが、精度が落ちることがある |
| QAT | 学習中に量子化誤差を模擬する | 手間は増えるが、精度を保ちやすい |
PTQは、すでに学習済みのモデルに対して使いやすい方法です。代表的な入力を流して、活性値の範囲を観測し、scaleやzero-pointを決めます。QATでは、学習中から「量子化されたらどう見えるか」をモデルに経験させます。そのため、量子化後の精度低下に強くなります。
数字で見ると、量子化のうれしさがはっきりします。FP32は1つの値に4byte使いますが、INT8なら1byteで済みます。単純に考えれば、モデルサイズとメモリ帯域はおよそ4分の1になります。メモリ帯域は、GPUやアクセラレータが重みを読み込む速さの制約になっていることが多いため、計算そのものが速くなるだけでなく、重みを読みに行く時間そのものが短くなるという効果もあります。もちろん、実際の高速化率は、ハードウェアがINT8演算にどれだけ最適化されているか、モデルのどの部分がボトルネックになっているかによって変わるため、「4分の1のサイズ」がそのまま「4倍速い」を意味するとは限りません。それでも、まず疑うべき効果の大きさとして、この4byte対1byteという比率を覚えておくと、量子化の効果を見積もりやすくなります。
実務で注意したいのは、量子化は単に型を変えるだけではないという点です。外れ値がある層、分布が大きく偏る活性値、LayerNormやsoftmax周辺などは、量子化で精度が落ちやすいことがあります。層ごとの精度劣化を見て、必要なら一部の層だけFP16やFP32に残す判断もします。
量子化をさらに極端に進めると、重みや活性値をほぼ2値にするBinary Netにつながります。
13. Binary Netは極端な低精度化で計算を置き換える
Binary Netでは、重みや活性値を ${-1,+1}$ のような2値に制限します。
$$
w_b=\operatorname{sign}(w),\qquad x_b=\operatorname{sign}(x)
$$
こうすると、通常の乗算を、ビット演算に近い形で置き換えられます。$-1,+1$ のベクトル同士の内積を考えます。長さを $n$、同じ符号の位置数を $m$ とすると、同じ符号の位置は積が $+1$、違う符号の位置は積が $-1$ です。したがって内積は
$$
m-(n-m)=2m-n
$$
になります。ビット表現では、同じ符号かどうかをXNORで調べ、同じ位置数をbitcountで数えればよいので、通常の浮動小数点乗算より軽くできます。
ただし、学習は難しくなります。$\operatorname{sign}$ はほとんどの場所で勾配が0で、通常の逆伝播をそのまま通せません。そこで、訓練中は実数の潜在重みを持ち、前向き計算では2値化し、逆向き計算では近似的に勾配を通す方法を使います。これを直通推定器、Straight-Through Estimatorと呼びます。
直感的には、前向きでは
$$
w_b=\operatorname{sign}(w)
$$
を使い、逆向きでは
$$
\frac{\partial w_b}{\partial w}\approx1
$$
のように扱います。実装によっては、$|w|\le1$ の範囲だけ勾配を通すなどの工夫をします。
Binary Netは、専用ハードウェアや極端に軽い推論が必要な場面では魅力があります。一方で、表現力が大きく制限されるため、一般的なGPU上で普通のモデルを高速化したいだけなら、INT8量子化や蒸留の方が扱いやすいことが多いです。
次は、行列そのものに構造を入れて計算を軽くするFFTと循環行列を見ます。
14. FFTと循環行列は構造を入れて行列積を軽くする
通常の $n\times n$ 行列は、$n^2$ 個のパラメータを持ちます。しかし、その行列が循環行列なら、必要なのは最初の1行または1列だけです。
たとえば、ベクトル
$$
c=(c_0,c_1,c_2,c_3)
$$
から作る循環行列は、行が順にずれていく形になります。
$$
C=
\begin{bmatrix}
c_0&c_3&c_2&c_1\
c_1&c_0&c_3&c_2\
c_2&c_1&c_0&c_3\
c_3&c_2&c_1&c_0
\end{bmatrix}
$$
この行列とベクトル $x$ の積は、循環畳み込みと同じです。
$$
Cx=c*x
$$
フーリエ変換を使うと、循環畳み込みは周波数領域での要素ごとの積になります。
$$
Cx=\mathcal F^{-1}\left[\mathcal F(c)\odot\mathcal F(x)\right]
$$
これは、循環行列がフーリエ基底で対角化できることに対応しています。ざっくり書けば、
$$
C=\mathcal F^{-1}\operatorname{diag}(\mathcal F(c))\mathcal F
$$
です。
何がうれしいのでしょうか。通常の行列積は、計算量がおおよそ $O(n^2)$ です。一方、FFTを使えば、計算量はおおよそ
$$
O(n\log n)
$$
になります。パラメータ数も $n^2$ から $n$ に減ります。
たとえば、$n=4096$ の行列なら、通常は
$$
4096^2=16,777,216
$$
個のパラメータが必要です。循環行列なら4096個で済みます。
ただし、これは強い構造制約です。任意の行列を自由に学習できるわけではありません。表現力をかなり制限する代わりに、パラメータと計算を減らします。さらに、FFTのオーバーヘッドやハードウェアの得意不得意もあるので、小さい行列では期待ほど速くならないことがあります。
この技術は、CNNに限った話ではありません。大きな線形変換全般に対して、「行列に構造を入れて軽くする」という考え方の一例です。
次は、学習済みモデルを別のタスクへ使う転移学習と、新しいデータで更新し続ける増分学習を見ます。
15. 転移学習と増分学習は新旧タスクの折り合いを取る
転移学習では、大きなデータで学習したモデルを、新しいタスクの出発点として使います。画像ならImageNetで事前学習したCNNやViTを、自分の分類タスクへ微調整する、という使い方が典型です。
実務では、次のような流れになります。
- 事前学習済みモデルを読み込む
- 最後の分類層を新しいクラス数に合わせて置き換える
- 最初はbackboneを固定し、分類層だけを学習する
- 必要なら、backboneの後ろの層から少しずつ解凍する
- 低めの学習率で全体を微調整する
これは、第3回で見たCNNの階層的特徴ともつながります。浅い層はエッジや色、模様のような一般的な特徴を持ちやすく、深い層は元タスクのクラスに近い特徴を持ちやすいです。そのため、新しいデータが少ないときは、浅い層をいきなり大きく動かさない方が安定することがあります。
増分学習では、新しいタスクや新しいデータが順に来ます。ここで問題になるのが、破滅的忘却です。新しいデータだけで学習すると、古いタスクの性能が落ちることがあります。
たとえば、最初に10種類の商品を分類するモデルを作り、後から新しい3種類の商品を追加するとします。新しい3種類の画像だけで学習すると、モデルは古い10種類を見なくなり、古いクラスの境界を壊してしまうことがあります。
なぜこれが起きるのか、もう少し踏み込んで考えてみます。ニューラルネットワークは、10種類の商品を分類するために、すべてのクラスが同じ重みを共有しながらクラス同士の境界線を引いています。新しい3種類だけを見て学習を続けると、勾配降下法はその3種類をうまく分けることだけを目的に重みを更新していきます。このとき更新の対象になっている重みは、古い10種類の境界線も同時に引いていた、まさにその重みです。古いデータをもう一度見せない限り、勾配は「古い境界線を保ってほしい」という情報を一切受け取れないため、新しい3種類にとって都合のよい方向にだけ重みが動いてしまい、結果として古い境界線が意図せず崩れます。これが破滅的忘却の仕組みです。第2回で見た、勾配は今見せられているデータに対する失敗だけを反映するという性質が、そのまま忘却の原因にもなっているということです。
対策として、次のような方法があります。
| 方法 | 考え方 | 注意点 |
|---|---|---|
| 新旧データを混ぜる | 古いデータも一緒に見せる | データ保存が必要で、訓練が重い |
| 重み付きサンプリング | 新旧データを均等にではなく、意図した比率でサンプリングする | 比率を誤ると新しいデータに埋もれるか、古いデータに引っ張られすぎる |
| リプレイ | 古い代表サンプルだけを少量残す | 代表サンプルの選び方が重要 |
| 蒸留 | 古いモデルの出力を新モデルに保たせる | 古いモデルの癖も残る |
| 重み正則化 | 重要なパラメータを大きく動かさない | 重要度推定が難しい |
このうち重み付きサンプリングは、実務で特に効きやすい工夫です。新しいデータがまだ少ないうちに新旧を単純に混ぜると、新しいクラスのサンプル数が古いクラスに埋もれてしまい、学習があまり進みません。逆に新しいデータだけを優先しすぎると、忘却が進みます。新しいクラスを少し多めに、しかし古いクラスもゼロにはしない、という比率を意図して選ぶことで、新しいクラスの学習速度と古いクラスの保持のバランスを取ります。
場面適応の目的関数は、次のように読めます。
$$
J=\alpha J_{task}+(1-\alpha)J_{KLD}
$$
$J_{task}$ は新しいタスクの損失です。$J_{KLD}$ は、古いモデルと新しいモデルの出力分布が離れすぎないようにする損失です。$\alpha$ が大きいほど新タスクを重視し、$\alpha$ が小さいほど旧モデルの挙動を守ります。
実務で大事なのは、新タスクの精度だけを見ないことです。増分学習では、少なくとも次の3つを同時に見ます。
| 指標 | 見ること |
|---|---|
| 新データの性能 | 新しいタスクを学べているか |
| 旧データの性能 | 古い知識を壊していないか |
| 全体の性能 | 新旧のバランスが取れているか |
新しいデータが少ないときほど、過学習と忘却が起きやすくなります。低い学習率、早期停止、リプレイ、蒸留を組み合わせて、急にモデルを動かしすぎないようにします。
最後に、これまでの「判別するモデル」と少し違う、データを生成するGANを見ます。
16. GANは生成器と判別器を競わせて分布を近づける
GAN、Generative Adversarial Networkは、生成器 $G$ と判別器 $D$ を競わせて学習する生成モデルです。
生成器は、ノイズ $z$ から偽サンプル $G(z)$ を作ります。判別器は、入力が本物のデータ $x$ なのか、生成器が作った偽物 $G(z)$ なのかを見分けます。
基本的な目的関数は、
$$
\min_G\max_D\ \mathbb E_{x\sim p_{data}}\log D(x)+\mathbb E_{z\sim p_z}\log\left[1-D(G(z))\right]
$$
です。
判別器 $D$ は、本物に高い確率、偽物に低い確率を出したい。生成器 $G$ は、判別器をだまして偽物にも高い確率を出させたい。この2つの目的が反対方向を向いているため、adversarial、つまり敵対的と呼ばれます。
固定された生成器 $G$ に対して、理想的な判別器を考えると、各点 $x$ で
$$
D^*(x)=\frac{p_{data}(x)}{p_{data}(x)+p_g(x)}
$$
になります。本物分布 $p_{data}$ が大きいところでは $D^*(x)$ は1に近く、生成分布 $p_g$ が大きいだけのところでは0に近くなります。もし $p_g=p_{data}$ なら、
$$
D^*(x)=\frac12
$$
です。つまり、理想的な均衡では、判別器は本物と偽物を見分けられません。
ただし、実際の学習では、生成器に元の目的関数をそのまま使うと勾配が弱くなることがあります。判別器が強すぎて $D(G(z))$ がほぼ0になると、生成器の学習が進みにくくなるためです。そこで実務では、生成器に非飽和損失を使うことがよくあります。
$$
L_G=-\mathbb E_z\log D(G(z))
$$
これは、「偽物を本物だと判定させる」方向に生成器を直接動かす損失です。
実務での訓練手順は、次のように交互に進みます。
- 本物データを判別器に入れ、本物として学習する
- ノイズから偽物データを作り、判別器に入れ、偽物として学習する
- 判別器を固定し、生成器が作った偽物を判別器に入れる
- 判別器が本物だと思うように、生成器を更新する
ここで、判別器を更新するときには、生成器が作った偽物をいったん計算グラフから切り離します。そうしないと、判別器を訓練したい場面で生成器にも勾配が流れてしまいます。一方、生成器を更新するときは、判別器を通した出力から生成器へ勾配を戻す必要があります。
GANの難しさは、損失が下がれば必ず良い画像になる、とは限らないことです。主な問題は次の通りです。
| 問題 | 何が起きるか | 対策の例 |
|---|---|---|
| 訓練不安定 | $G$ と $D$ の強さが崩れる | 学習率、更新回数、正則化を調整する |
| モード崩壊 | 似たサンプルばかり生成する | minibatch discrimination、WGAN系、データ拡張など |
| 評価が難しい | 見た目と損失が一致しにくい | FID、生成例の確認、下流タスク評価を併用する |
| データ偏り | 学習データの偏りをそのまま生成する | データ監査、フィルタリング、用途制限を行う |
GANを見るときは、生成器だけでなく、判別器との力関係を見る必要があります。判別器が弱すぎると生成器は学べません。判別器が強すぎても、生成器へ有用な勾配が戻りにくくなります。2つのモデルを同時に育てるところが、GANの面白さであり難しさです。
ここまでで、学習を速くする技術、推論を軽くする技術、新旧データを扱う技術、生成モデルを見ました。最後に、実務で選ぶときの観点を整理します。
17. 実務で高度化技術を選ぶためのチェックリスト
複数GPU訓練を見る
- モデルは1枚のGPUに載るのか、載らないのか
- データ並列でglobal batch sizeが大きくなりすぎていないか
- 勾配平均の通信がボトルネックになっていないか
- 同期方式で遅いworkerを待ちすぎていないか
- 非同期やローカル更新を使う場合、stale gradientや精度劣化を測っているか
- BatchNormの統計をGPUごとに取るのか、同期するのかを決めているか
学習の省メモリ・高速化を見る
- GPUメモリ不足の原因は、モデルサイズなのか、batchサイズなのか、中間結果の保持なのかを切り分けているか
- 勾配チェックポイントを使う場合、増える計算時間とのトレードオフを確認しているか
- 混合精度学習で、損失スケーリングによりアンダーフローを防いでいるか
- 勾配累積を使う場合、実効batchサイズと学習率の関係を見直しているか
推論圧縮を見る
- 何を減らしたいのか。モデルサイズ、レイテンシ、スループット、メモリ、電力のどれか
- SVDで置き換える層は、本当に低ランク近似に向いているか
- 枝刈りは非構造化ではなく、実際に速くなる構造化削減になっているか
- パラメータ共有で、メモリ削減だけでなく推論速度も測っているか
- 蒸留では、先生モデルの品質と温度 $T$、混合比 $\alpha$ を検証しているか
- 量子化では、校正データが実運用の入力分布を代表しているか
- 一部の層だけ高精度に残す必要がないか
転移・増分学習を見る
- 新しいデータ量に対して、全層を動かしすぎていないか
- 旧タスクの評価データを残しているか
- 新タスク精度だけでなく、旧タスク精度も同時に見ているか
- リプレイ、蒸留、重み正則化のどれを使うべきか
- データ分布が変わったのか、クラスが増えたのか、ラベル定義が変わったのかを区別しているか
GANを見る
- 生成器と判別器のどちらが強すぎるかを見ているか
- 生成例だけでなく、モード崩壊や多様性を確認しているか
- 損失だけで品質を判断していないか
- 生成データの用途、偏り、権利、プライバシーを確認しているか
18. この第4回のまとめ
第4回では、モデルを現場で学習し、軽くし、更新し、生成に使うための技術を整理しました。
- 複数GPU訓練では、データを分けるデータ並列と、モデルを分けるモデル並列を区別する
- データ並列の基本は、各GPUで計算した勾配を平均して同じパラメータ更新を行うこと
- 同期SGD、非同期SGD、モデル平均は似て見えるが、待ち時間、stale gradient、更新経路が違う
- モデル並列やパイプライン並列は、巨大モデルを載せるための技術だが、通信と待ち時間が重要になる
- 推論高速化では、どの計算や表現を近似しているのかを明確に見る
- SVDは、大きな行列を低ランクの2つの行列に分けて、パラメータと計算を減らす
- 枝刈りは、実際に速くしたいなら、チャンネルやheadのような構造を削ることが重要
- パラメータ共有は、多数の重みを少数の代表値とインデックスで表す
- 知識蒸留は、先生モデルの確率分布に含まれるクラス間の関係を学生モデルへ渡す
- 量子化は、実数を低精度の目盛りへ写し、整数計算や低精度浮動小数点で推論を軽くする
- Binary Netは、乗算をビット演算に近づけるが、表現力と学習の難しさに注意が必要
- FFTと循環行列は、行列に構造を入れることで、パラメータ数と計算量を減らす
- 転移学習と増分学習では、新しいタスクを学びながら、古い知識を壊しすぎないことが重要
- GANは、生成器と判別器の競争を通じて、生成分布をデータ分布へ近づける
第1回の行列、第2回の学習、第3回のCNNとRNNは、この第4回でもそのまま土台になっています。複数GPU訓練では勾配をどう集めるかを考え、SVDやFFTでは行列の構造を使い、蒸留や増分学習では損失を組み合わせ、GANでは2つのネットワークの目的関数を同時に読みます。
ここまでで、深層学習モデルを作る、学習する、構造を読む、現場で軽く使う、という流れが一通りつながりました。次にさらに進むなら、AttentionやTransformer、拡散モデル、自己教師あり学習のように、現代の大規模モデルを支える仕組みを同じ視点で読むことができます。
19. 深層学習の一本の糸:4回分を最も簡単な言葉でつなぐ
ここまで4回にわたって、行列やSVD、前向き計算や逆伝播、CNNやRNN、複数GPUや圧縮や蒸留や生成モデルと、たくさんの道具を見てきました。最後に、専門用語をいったん脇へ置いて、この4回すべてに共通する1本の糸を、できるだけ簡単な言葉でつないでおきます。
深層学習を、台所で料理を作る話に置き換えてみます。
- 第1回は、材料の量り方と、失敗の数え方を決める回でした。行列は材料の並べ方、損失関数は「まずい」をどう数字にするかの決め方、正則化は「凝りすぎない」ための好みでした。
- 第2回は、実際に料理を作る回でした。材料を混ぜて味を作り(前向き計算)、味見をして失敗を数値にし(損失)、どの工程が失敗の原因だったかを1つずつさかのぼって突き止め(逆伝播)、その工程だけを少し直します(パラメータ更新)。これを何度も繰り返すのが学習です。
- 第3回は、材料の並び方に合わせて、道具の使い方を変える回でした。画像は空間に広がっているので、同じ小さな型(カーネル)を場所を変えて繰り返し使い、系列は時間に沿って並んでいるので、同じ手順(重み)を時刻を変えて繰り返し使います。
- 第4回は、できあがったレシピを、実際の店で出せるようにする回でした。注文が多いなら厨房を増やし(複数GPU、勾配チェックポイントや混合精度)、材料や手順を削っても味が大きく変わらないなら削り(SVD、枝刈り、パラメータ共有、蒸留、量子化)、新しいメニューを覚えさせるなら前のメニューを忘れさせすぎないようにし(転移学習・増分学習)、時には本物そっくりの新しい皿を作る練習もします(GAN)。
この比喩から取り出せる、実務でいちばん使い回しの利く問いは、次の1つに集約できます。
何を入力とし、何を失敗として数え、その失敗の責任をどこまで正確にさかのぼれて、そこから得た知識を、どこまで安く、軽く、安全に使い回せるか。
新しいモデルを作るとき、新しいデータで既存モデルを更新するとき、大きなモデルを現場に載せるとき、どんな場面でもうまくいかない原因は、たいていこの問いのどこかに答えられていないことにあります。入力のshapeを取り違えているなら第2回・第3回で見た構造の問題であり、損失の設計がビジネス上の失敗の重みと合っていないなら第1回で見た問題であり、勾配が消えたり爆発したりするなら第2回で見た問題であり、モデルが重すぎて現場で回らないなら第4回で見た問題です。
実務でも、分析でも、実験でも、うまくいかないときに立ち止まって、「今、この4回のうちどの糸が切れているのか」を自問してみてください。糸の切れ目さえ分かれば、対処の手がかりは、この4回のどこかにすでに書いてあります。