
「破局的忘却」を実際に起こしてみたら、Task A精度は0.9834から0.0000まで消えた。10%のリハーサルでも一度は9.9%まで沈んでから戻ってきた
ニューラルネットワークに新しいタスクを学習させ続けると、前に学習した古いタスクのことを完全に忘れてしまう——**破局的忘却(catastrophic forgetting)**と呼ばれる現象だ。人間なら新しい仕事や趣味に没頭している間に、前に得意だったことの感覚が鈍っていく、という程度で済むことが多い。しかしニューラルネットワークの場合、この「忘れ方」はもっと極端で、しかも不可逆に近いとよく言われる。
前回の記事では、手書き数字0-4で事前学習した重みを5-9の分類に転移させる実験をした。今回はその続きとして、同じ64→64(ReLU)の手書きMLPを使い、今度は「転移」ではなく「上書き」の方、つまり同じネットワークにTask Aを学習させたあと、そのままTask Bだけを学習させ続けたらTask Aの精度はどうなるかを実際に測ってみる。そしてもう一つ、崩壊を防ぐ代表的な対策であるリハーサル(rehearsal)——新しいタスクを学習する合間に、古いタスクのデータもごく少量混ぜ続ける手法——がどれだけ効くのかも同時に検証する。
結論から言うと、崩壊のスピードは想像以上に速く、そして「リハーサルがあれば安心」という単純な話にもならなかった。
実験設定: 同じネットワークにTask A→Task Bを順番に学習させる
データは前回と同じくscikit-learnのload_digits(8×8ピクセルの手書き数字、1797枚)。数字を0〜4(Task A)と5〜9(Task B)に分けるところも前回と同じだ。
TASK_A = [0, 1, 2, 3, 4]
TASK_B = [5, 6, 7, 8, 9]
task A: train n=720, test n=181
task B: train n=716, test n=180
ここで、モデルが実際に見ている「8×8ピクセルの手書き数字」がどんなものかを先に見ておいてほしい。前回の記事から読んでくれている人には繰り返しになるが、この後「数字が別の数字に化ける」という話が出てくるので、入力がどのくらいの解像度の絵なのかを頭に置いておくと実感が湧きやすいはずだ。

MNISTの28×28よりさらに粗い、64ピクセルしかないモザイク状の画像だ。この64個の画素値がそのまま64次元の入力ベクトルになる。そして今回の実験でずっと測り続けるのは、Task B(5-9)の学習を進めている間に、モデルがTask Aのテスト画像181枚をどれだけ正しく分類できるかというTask Aテスト精度。これを「まだ忘れていない度合い」のものさしにして、学習ステップごとに記録していく。
ネットワークも前回と同じ、64次元入力→隠れ層64ユニット(ReLU)の手書きMLPで、Adamも自前実装のものをそのまま使う。ただし出力層だけ前回と違う設計にした。前回は「Task A用に5クラスsoftmax」「Task B用に5クラスsoftmax」と別々の出力ヘッドを使っていたが、今回はTask AとTask Bを同じ1つの10クラスsoftmax出力層で扱う。忘却を観測するには、両タスクが同じ出力空間を奪い合う状況を作る必要があるからだ(出力ヘッドが別々なら、Task Bをいくら学習してもTask A用のヘッドはそもそも触られないので、忘却という現象自体が起きない)。
まずTask Aの720枚で、前回のソースモデルと同じ400エポックのフルバッチ学習をする。
after phase1 (task A only, 400 epochs): A train acc=1.0000 A test acc=0.9834
訓練精度100%、テスト精度98.34%まで到達した「よく学習の進んだ状態」を、ここから先の実験の共通の出発点(チェックポイント)にする。
条件1: リハーサルなしでTask Bだけを学習させ続ける
このチェックポイントから、バッチサイズ32のミニバッチAdamで、Task Bの訓練データだけを使って学習を続ける(Task Aのデータは一切見せない)。3000ステップ——Task Bの訓練データ(716枚)を約134エポック分——回し、5ステップごとにTask Aのテスト精度を記録した。
結果は、正直ここまで速いとは思っていなかった。
step= 1 A_acc=0.9834
step= 10 A_acc=0.7624
step= 15 A_acc=0.2873
step= 20 A_acc=0.0718
step= 30 A_acc=0.0000
ステップ10の時点でTask A精度は90%を割り、ステップ15で50%を割り、ステップ30でちょうど0.0000になる。 バッチサイズ32なので、ステップ30まではTask Bの画像を960枚見せただけ——Task Bの訓練データを1.3エポック分見せただけの計算だ。しかもこの0.0000は一時的な底ではない。3000ステップ(134エポック分)が終わるまで、一度も0.0000から動かなかった。181枚のテスト画像、どれ一つとして正しい数字に分類できない状態がそのままずっと続く。
なぜこんなに速く、しかも完全に0まで落ちるのか
崩壊のスピードにも「なぜ0.0000という完全な値になるのか」にも納得がいかなかったので、内部で何が起きているか掘り下げてみた。まず疑ったのは出力層のバイアス(各クラスの「出やすさ」を決める項)だ。Task Bしか見せていないのだから、Task Aの5クラス分のバイアスがまとめて下がって出番を失っているだけではないか、という仮説だ。
実際に出力層の平均logit(softmaxに入れる前の生スコア)を、Task Aの5クラス分とTask Bの5クラス分でステップごとに追ってみた。

step= 1 logit_A= 0.700 logit_B=-8.630
step=10 logit_A=-5.192 logit_B=-0.512
step=30 logit_A=-8.905 logit_B= 2.749
step=60 logit_A=-10.463 logit_B= 2.668
ステップ1ではTask A側のlogitが0.700、Task B側は-8.630と、Task Aが優勢な状態から始まる。それがステップ10前後で逆転し、ステップ60ではTask A側が-10.463、Task B側が+2.668と完全に入れ替わっている。ここまでは予想通りだが、意外だったのは出力層のバイアス項そのものはほとんど動いていなかったことだ。
出力バイアス(Task Aクラス平均): 0.085 → -0.084 (Δ約0.17)
出力バイアス(Task Bクラス平均): -0.112 → 0.043 (Δ約0.155)
バイアスの変化量はこの程度で、logitの変化量(9〜11ポイント)の説明には全く足りない。つまり「Task Aクラスの出番を意図的に減らす」という単純な調整ではなく、隠れ層の重み(W1)自体がTask B用に作り替えられていて、Task Aの画像を通したときの特徴表現そのものが変質している、というのが実態に近い。実際、この3000ステップの間に隠れ層の重みW1が動いた距離(L2ノルム)は15.61と、決して小さくない。前回の記事で見た「一部のユニットが死ぬ」話とは違い、隠れ層全体がじわじわとTask B仕様に上書きされていく、というイメージの方が近そうだ(ちなみにTask Aデータに対する死んだユニットの割合は、phase2開始前で29.69%、リハーサルなし終了後で34.38%と、大きくは変わらなかった。今回の崩壊の主犯は「死んだユニット」ではなさそうだ)。
「忘れる」というより「新しい数字に化ける」
もう一つ気になったのが、崩壊後のTask A画像が実際に何と予測されているかだ。単に自信をなくして曖昧な予測になっているだけなのか、それとも何か特定のクラスに引っ張られているのか。

結果ははっきりしていた。
真の数字0(n=36) → 83.3%が「6」と予測
真の数字1(n=37) → 75.7%が「8」と予測
真の数字2(n=35) → 54.3%が「8」、31.4%が「9」と予測
真の数字3(n=37) → 67.6%が「9」、18.9%が「8」と予測
真の数字4(n=36) → 66.7%が「6」、22.2%が「7」と予測
割合の表だけだと実感が湧きにくいので、実際に「化けた」画像も並べてみる。どれも崩壊前のチェックポイントでは正しく分類できていた画像だ。

当たり前だが、変わったのは画像の側ではない。入力は1ピクセルも同じままで、ネットワークの中身が変わっただけなのに、同じ「0」が崩壊の前後で「0」から「6」に読み替えられている。
崩壊は「わからなくなって迷子になる」というより、**元の数字ごとにほぼ決まった行き先(新しいクラス)に系統的に「化ける」**という形だった。0はほぼ確実に6として、1はほぼ確実に8として処理される。これは共有された隠れ層の特徴表現が、Task Bの5クラス用に再編成された結果、たまたま特徴が近い(あるいは重みの都合上そう扱われやすい)Task Bのクラスにそのまま吸収されている、ということなのだと思う。忘却は「情報が消える」というより「情報が別のラベルに再利用される」に近い。
条件2: 10%リハーサルを混ぜる
次に、同じチェックポイントから学習をやり直すが、今度はTask Bのミニバッチ(32枚)のうち3枚を毎回Task Aの訓練データからランダムに差し替える。バッチの約10%が常にTask Aのリハーサルになるという条件だ。バッチの抽出に使う乱数列はリハーサルなし条件と揃えてあるので、両条件は本当に「リハーサルの有無」だけの違いになっている。

正直、これが一番意外だった。「リハーサルを入れておけば崩壊は防げる」と予想していたのに、リハーサルありでも最初の落ち方はリハーサルなしとほとんど同じだったのだ。
step= 5 A_acc=0.9503
step=10 A_acc=0.8011
step=15 A_acc=0.3425
step=20 A_acc=0.1602
step=25 A_acc=0.0994 ← 最小値
step=30 A_acc=0.2652
step=50 A_acc=0.7624
step=100 A_acc=0.9006
step=3000 A_acc=0.9448 ← 最終値
ステップ25の時点で、Task A精度は**9.94%**まで沈む。リハーサルなし条件が同じ時点でほぼ0%まで落ちていることを考えると、「リハーサルが最初から崩壊を食い止めている」とは到底言えない下がり方だ。バッチの90%を占めるTask Bの勾配が、残り10%のTask Aの勾配をこの段階では圧倒してしまっている。
ただしそこから先がリハーサルなし条件とは決定的に違った。ステップ25を底に、そこから150ステップほどかけてじわじわと回復し、ステップ100で90%、最終的にステップ3000では**94.48%**まで戻ってきた。念のため他の3つのseedでも同じ形になるか確認したところ、いずれも「ステップ20〜25あたりで4〜17%まで沈み、その後数百ステップかけて回復する」という同じパターンが再現された。一度きりの偶然ではなさそうだ。
15シードで平均を取る
単発の実行だけでは心もとないので、データ分割・初期値・バッチの抽出をすべて変えた15個のseedで同じ実験(phase2は1500ステップに短縮)を繰り返し、最終的な精度を平均した。

Task A 開始時: 平均0.9948 (std 0.0059)
Task A リハーサルなし最終: 平均0.0011 (std 0.0041) ※15回中14回が完全に0.0000、残り1回も0.0166
Task A リハーサルあり最終: 平均0.9676 (std 0.0129)
Task B リハーサルなし最終: 平均0.9822 (std 0.0074)
Task B リハーサルあり最終: 平均0.9752 (std 0.0090)
(細かい話だが、開始時の平均が99.48%と、ここまで単発runで使ってきた98.34%より高いことに気づいた人がいるかもしれない。15シードではデータの訓練/テスト分割も毎回引き直していて、単発runで使ったseed 0の分割はたまたま開始時精度が低めに出る部類だった、というだけの話だ)
リハーサルなしでのTask A崩壊(平均0.11%)は、単発の偶然ではなく15回中ほぼ毎回起きる、かなり頑健な現象だった。 一方リハーサルを入れると、開始時99.48%からの下げ幅は平均でわずか2.7ポイント(96.76%)に収まる。そしてTask B側の精度は、リハーサルなし98.22%に対しリハーサルあり97.52%と、わずか0.7ポイントしか下がっていない。バッチの1割をTask Aに割いても、Task B自体の学習はほとんど犠牲になっていなかった。
重み自体の移動量は、むしろリハーサルありの方が大きかった
もう一つ、直感に反していたのでここに書いておきたい。隠れ層の重みW1がチェックポイントからどれだけ移動したか(L2ノルム)を条件間で比べると、
W1移動量(3000ステップ後): リハーサルなし=15.61 リハーサルあり=23.99
最終的にTask Aをよく覚えていた「リハーサルあり」の方が、重み空間ではむしろ大きく動いていた。 忘れなかった方が変化が小さいはずだ、という素朴な予想とは逆の結果だ。想像でしかないが、リハーサルなしは「Task Bだけに最適化すればいい」という一方向の目的地に向かって比較的まっすぐ進めるのに対し、リハーサルありは毎ステップTask AとTask Bという相反する要求の綱引きの中で位置を探り続けることになり、その分だけ移動距離(経路長)が伸びるのではないかと思う。最終的な「今どこにいるか」ではなく「そこにたどり着くまでにどれだけ揺さぶられたか」を測ると、話は単純ではなくなる。
崩壊も、リハーサルも、思っていたよりずっと生々しかった
今回の実験で一番強く印象に残ったのは、崩壊のスピードそのものだった。Task Bの画像を1000枚も見せないうちに、98.34%あった精度が完全にゼロになる。しかもその後は3000ステップ経っても指一本ぴくりとも動かない。「じわじわ忘れていく」というよりは、「ある瞬間にスイッチが切り替わって、二度と戻らない」という感覚に近い。
そしてリハーサルについても、当初思っていたような「保険をかけておけば安心」という綺麗な話ではなかった。10%のリハーサルは、崩壊そのものを防ぐわけではない——最初の20〜25ステップでは、リハーサルの有無にほとんど関係なく精度は同じように地の底まで落ちる。リハーサルが効いているのは、そこから先の「戻ってこられるかどうか」の部分だった。少量でも古いタスクの情報がバッチに混ざり続けている限り、一時的にほぼゼロまで落ちても、そこから這い上がる道が残されている。情報が完全にゼロになる(リハーサルなし)と、その道自体が失われる。
これは、何かに没頭している間に前のスキルの感覚が鈍っていく感覚とよく似ている気がする。新しいことに全力を注いでいる最中は、古い方の感覚が目に見えて落ちていくのを止められないことがある。今回の実験で言えば、リハーサルをしていてもステップ25時点では9.94%まで沈んでいた、その落ち込み自体は避けられない。でも、その最中にほんの少しでも古い方に触れ続けていれば(バッチの1割で十分だった)、いったん落ちたところから戻ってくる道は残る。逆にその接点を完全に断ってしまうと、戻り道そのものがなくなって、いくら時間をかけても元には戻らない。「少しでも触れ続けること」の意味は、落ち込みそのものを防ぐことではなく、落ち込んだ後にまだ戻れる状態を保っておくことにあるのかもしれない。
まとめ
- 手書き数字0-4(Task A)を98.34%まで学習させたMLPに、Task B(5-9)だけをリハーサルなしで学習させ続けたところ、Task A精度はステップ10で90%割れ、ステップ15で50%割れ、ステップ30でちょうど0.0000に到達し、その後3000ステップ(134エポック分)経っても一度も0.0000から動かなかった
- 崩壊の主因を調べると、出力層のバイアス項の変化はごくわずか(Δ約0.17)で、Task Aクラスとの平均logitがステップ10前後で逆転する背景には隠れ層の重み(W1、移動量15.61)自体がTask B用に作り替えられていたことがあった
- 崩壊後のTask A画像は曖昧に迷うのではなく、数字0→「6」(83.3%)、数字1→「8」(75.7%)のように、ほぼ決まった新しいクラスへ系統的に「化けて」いた
- バッチの10%をTask Aのリハーサルに充てても、崩壊そのものは防げず、ステップ25時点でTask A精度は9.94%まで沈んだ(15シードでも同じ「一度沈んでから回復する」パターンを確認)。ただしそこから150ステップほどかけて回復し、最終的に94.48%(15シード平均96.76%)まで戻った
- Task B側の最終精度は、リハーサルなし98.22%に対しリハーサルあり97.52%と、10%を古いタスクに割いてもわずか0.7ポイントしか犠牲にならなかった
- 意外なことに、隠れ層の重みの移動量(L2ノルム)はリハーサルなしよりリハーサルありの方が大きかった(15.61 vs 23.99)。最終的によく覚えていた方が、そこにたどり着くまでの経路はむしろ長く揺さぶられていた


