Hayami Kaito Web
~/tech

過学習と正則化のしくみ:L1・L2・ドロップアウト・早期終了・交差検証

published

機械学習のモデルを作る目的は、学習に使ったデータをうまく説明することではなく、まだ見たことのないデータに対して正しく答えることです。ところが、モデルが学習用のデータに合わせすぎると、ノイズやたまたまの偏りまで覚えてしまい、新しいデータでは外れるようになります。これを 過学習(overfitting)と呼び、過学習を防ぐための工夫をまとめて 正則化と呼びます。

この記事は ニューラルネットワークの学習のしくみ、最適化手法のしくみ の続きです。前の 2 本では「学習用のデータでの損失をどう下げるか」を説明しましたが、この記事では「下げすぎるとなぜ困るのか、どう防ぐか」を説明します。

結論を先に書くと、過学習とその対策は次のように整理できます。

  • 過学習は、モデルの自由度に対してデータが少ないときに起きる: 学習用のデータでの誤差は 0 に近づくのに、テスト用のデータでの誤差は大きくなる
  • 誤差は「バイアス」と「バリアンス」に分けて考える: 単純すぎるモデルはいつも同じように外れ(バイアス)、複雑すぎるモデルはデータが変わるたびに答えが大きく変わる(バリアンス)
  • 正則化で自由度を抑える: 重みを小さく保つ(L2・L1)、学習を早めに止める(早期終了)、ニューロンをランダムに休ませる(ドロップアウト)、データを水増しする(データ拡張)
  • 調整の値は交差検証で選ぶ: テスト用のデータには最後まで触らず、学習用のデータを分けて評価する

説明の途中に出てくる計算例は、Python と NumPy で実際に動かした結果です。

環境

計算例を動かした環境です。

項目バージョン
Python3.13
NumPy2.5

過学習とは:学習用のデータに合わせすぎる

多項式で曲線を当てはめる

本当の関係が y = sin(2πx) で、そこにノイズが加わったデータが 10 個あるとします。これに 1 次式(直線)・3 次式・9 次式を当てはめ、学習用の 10 個と、別に用意したテスト用の 1,000 個で誤差を比べてみます。誤差は、予測と正解の差の 2 乗の平均の平方根(RMSE)です。

import numpy as np

rng = np.random.default_rng(0)

def make_data(n):
    x = rng.uniform(0, 1, n)
    y = np.sin(2 * np.pi * x) + rng.normal(0, 0.2, n)  # 本当の関係は sin。ノイズを加える
    return x, y

x_train, y_train = make_data(10)
x_test, y_test = make_data(1000)

def features(x, degree):
    return np.vander(x, degree + 1, increasing=True)  # 1, x, x², …, x^degree

def rmse(w, x, y, degree):
    return float(np.sqrt(((features(x, degree) @ w - y) ** 2).mean()))

for degree in [1, 3, 9]:
    w = np.linalg.lstsq(features(x_train, degree), y_train, rcond=None)[0]
    print(degree, round(rmse(w, x_train, y_train, degree), 3), round(rmse(w, x_test, y_test, degree), 3),
          round(float(np.abs(w).max()), 1))

実行結果:

1 0.497 0.628 1.1
3 0.154 0.237 41.6
9 0.0 0.624 73199.6

左から、式の次数、学習用のデータでの誤差、テスト用のデータでの誤差、係数(重み)の絶対値の最大です。

次数学習用の誤差テスト用の誤差状態
10.4970.628直線では sin の山と谷を表せない(学習不足)
30.1540.237どちらも小さい。ちょうどよい
90.00.624学習用の 10 個は完全に通るが、テスト用では外れる(過学習)

9 次式には係数が 10 個あるので、10 個の点をぴったり通る曲線を必ず作れます。その結果、ノイズまで含めて点を通るように、点と点の間で大きく波打つ曲線になりました。係数の最大は 7 万を超えていて、大きな正と負の係数が打ち消し合って、かろうじて点を通っている状態です。

ノイズの標準偏差を 0.2 にしているので、どんなに良いモデルでもテスト用の誤差は 0.2 程度より小さくはなりません。学習用の誤差が 0.2 を大きく下回っているのは、ノイズまで覚えてしまったしるしです。

データを増やすと過学習は減る

同じ 9 次式のまま、学習用のデータの数を増やしてみます。上のコードの続きです。

# 9 次式のまま、学習用のデータを増やす
rng = np.random.default_rng(1)  # make_data が使う乱数を作り直す
for n in [15, 30, 100, 1000]:
    x_n, y_n = make_data(n)
    w = np.linalg.lstsq(features(x_n, 9), y_n, rcond=None)[0]
    print(n, round(rmse(w, x_n, y_n, 9), 3), round(rmse(w, x_test, y_test, 9), 3))

実行結果:

15 0.086 2.036
30 0.135 0.525
100 0.182 0.216
1000 0.203 0.206

15 個ではテスト用の誤差が 2.036 と大きく外れていますが、100 個で 0.216、1,000 個では 0.206 と、ノイズの大きさ 0.2 にほぼ届きました。学習用とテスト用の誤差の差も小さくなっています。データが十分に多ければ、点と点の間で勝手に波打つ余地がなくなるからです。過学習はモデルの複雑さだけでなく、データの量との釣り合いで決まります。

バイアスとバリアンス

誤差を 2 つに分ける

モデルの誤差は、次の 2 つ(とノイズ)に分けて考えられます。

誤差意味大きくなるのは
バイアス学習用のデータを何度作り直しても、平均して本当の値からずれている量モデルが単純すぎるとき
バリアンス学習用のデータを作り直すたびに、予測がどれだけばらつくかモデルが複雑すぎるとき

学習用のデータ(20 個)を 500 回作り直して、そのたびに多項式を当てはめ、x = 0.25(本当の値は sin(π/2) = 1)での予測を集めてみます。

import numpy as np

# 学習用のデータ(20 個)を 500 回作り直し、x = 0.25 での予測のばらつきを見る
rng = np.random.default_rng(0)
x0, truth = 0.25, np.sin(2 * np.pi * 0.25)  # 本当の値は 1
for degree in [1, 3, 9]:
    preds = []
    for _ in range(500):
        x = rng.uniform(0, 1, 20)
        y = np.sin(2 * np.pi * x) + rng.normal(0, 0.2, 20)
        w = np.linalg.lstsq(np.vander(x, degree + 1, increasing=True), y, rcond=None)[0]
        preds.append(np.vander([x0], degree + 1, increasing=True) @ w)
    preds = np.array(preds).ravel()
    bias2 = (preds.mean() - truth) ** 2
    print(degree, round(float(preds.mean()), 3), round(float(bias2), 4), round(float(preds.var()), 4))

実行結果:

1 0.498 0.2519 0.0285
3 0.993 0.0001 0.0085
9 0.983 0.0003 0.3559

左から、次数、500 回の予測の平均、バイアスの 2 乗、バリアンス(予測の分散)です。

  • 1 次式: 予測の平均が 0.498 で、本当の値 1 から大きくずれています(バイアスの 2 乗 0.2519)。何度データを作り直しても、直線では山の頂上に届きません
  • 3 次式: 平均 0.993 でほぼ正しく、ばらつきも 0.0085 と小さく、どちらも小さくなっています
  • 9 次式: 平均は 0.983 とほぼ正しいのに、バリアンスが 0.3559 と 3 次式の 40 倍以上です。データが変わるたびに、予測が大きく上下にぶれています

単純なモデルはバイアスが大きく、複雑なモデルはバリアンスが大きくなります。この 2 つの釣り合いを バイアスとバリアンスのトレードオフと呼びます。過学習は「バリアンスが大きすぎる状態」で、以下で説明する正則化は、少しバイアスを増やす代わりにバリアンスを大きく減らす方法だと言えます。

L2 正則化:重みを小さく保つ

損失に重みの大きさを足す

9 次式の過学習では、係数が何万という大きな値になっていました。そこで、損失に「重みの 2 乗の和」を足し、重みが大きくなるほど損をするようにします。

損失 = 誤差の 2 乗の和 + λ × 重みの 2 乗の和

これを L2 正則化と呼び、線形回帰に使ったものは リッジ回帰とも呼ばれます。λ(ラムダ)は正則化の強さで、大きいほど重みが小さく抑えられます。ニューラルネットワークでは 重み減衰(weight decay)とも呼ばれ、最適化手法の記事 で説明した AdamW もこの考え方を使っています。

9 次式のまま、λ を変えて当てはめてみます。最初のコードの続きです。

# L2 正則化(リッジ回帰):損失 = 2 乗誤差の和 + λ × 重みの 2 乗の和
def ridge(x, y, degree, lam):
    X = features(x, degree)
    penalty = lam * np.eye(degree + 1)
    penalty[0, 0] = 0  # 定数項(切片)は縮めない
    return np.linalg.solve(X.T @ X + penalty, X.T @ y)

for lam in [1e-9, 1e-7, 1e-5, 1e-3, 1e-1]:
    w = ridge(x_train, y_train, 9, lam)
    print(lam, round(rmse(w, x_train, y_train, 9), 3), round(rmse(w, x_test, y_test, 9), 3),
          round(float(np.abs(w).max()), 1))

実行結果:

1e-09 0.02 0.668 505.8
1e-07 0.042 0.505 346.7
1e-05 0.131 0.261 19.4
0.001 0.172 0.351 9.0
0.1 0.394 0.535 1.1

左から λ、学習用の誤差、テスト用の誤差、係数の絶対値の最大です。λ を大きくするほど係数は小さくなり(505.8 → 1.1)、学習用の誤差は上がっていきます。テスト用の誤差は、この 5 つの中では λ = 0.00001 の 0.261 がいちばん小さく、正則化なしの 0.624 から大きく改善して、3 次式の 0.237 に近づきました。λ をさらに大きくすると、今度は重みを縮めすぎて直線に近づき、学習不足になります。

同じ 9 次式でも、正則化の強さで「ちょうどよい複雑さ」に調整できるということです。

L1 正則化:効いていない重みを 0 にする

重みの絶対値を足す

損失に重みの 2 乗ではなく、絶対値の和を足すのが L1 正則化で、線形回帰に使ったものは ラッソ(Lasso)と呼ばれます。L1 正則化には、効いていない重みをちょうど 0 にする性質があります。

特徴が 10 個あるのに、本当に効いているのは最初の 3 個だけのデータで、L2(リッジ)と L1(ラッソ)を比べてみます。

import numpy as np

# 特徴が 10 個あるが、効いているのは最初の 3 個だけのデータ
rng = np.random.default_rng(0)
X = rng.standard_normal((50, 10))
true_w = np.array([3.0, -2.0, 1.5, 0, 0, 0, 0, 0, 0, 0])
y = X @ true_w + rng.normal(0, 1.0, 50)

def ridge_fit(X, y, lam):
    """L2 正則化(リッジ):式で一度に解ける"""
    return np.linalg.solve(X.T @ X + lam * np.eye(X.shape[1]), X.T @ y)

def lasso(X, y, lam, iters=500):
    """L1 正則化(ラッソ):重みを 1 つずつ最適な値に直す座標降下法"""
    w = np.zeros(X.shape[1])
    for _ in range(iters):
        for j in range(X.shape[1]):
            r = y - X @ w + X[:, j] * w[j]          # j 番目以外で説明できなかった残り
            rho = X[:, j] @ r
            w[j] = np.sign(rho) * max(abs(rho) - lam, 0) / (X[:, j] @ X[:, j])  # 小さければ 0 にする
    return w

print(np.round(ridge_fit(X, y, 20.0), 2))
print(np.round(lasso(X, y, 20.0), 2) + 0.0)  # + 0.0 で -0.0 を 0.0 にそろえる

実行結果:

[ 2.07 -1.46  1.05 -0.09  0.02  0.09 -0.09  0.24  0.1  -0.2 ]
[ 2.63 -1.69  1.2   0.    0.    0.    0.    0.    0.    0.  ]

1 行目の L2 では、効いていない 7 個の重みも −0.2〜0.24 の小さな値として残っています。2 行目の L1 では、7 個の重みがちょうど 0 になり、効いている 3 個だけが残りました。

違いは、重みを縮める力のかかり方にあります。L2 の罰は重みの 2 乗なので、重みが 0 に近づくほど縮める力も弱くなり、0 ちょうどにはなりにくくなります。L1 の罰は絶対値なので、重みが小さくても同じ強さで縮め続け、ある大きさより小さい重みは 0 に押し込まれます(コードの max(abs(rho) - lam, 0) の部分)。

L2 正則化(リッジ)L1 正則化(ラッソ)
足すもの重みの 2 乗の和重みの絶対値の和
重みの変わり方全体を少しずつ小さくする効いていない重みを 0 にする
向いている場面多くの特徴が少しずつ効いているときどの特徴が効いているかを絞り込みたいとき

早期終了:検証用の誤差が上がり始めたら止める

学習しすぎると過学習する

ニューラルネットワークでは、学習を長く続けるほど学習用のデータへの当てはまりが良くなり、ある時点から過学習が始まります。そこで、学習に使わない 検証用のデータの誤差を見ながら学習し、検証用の誤差がいちばん小さかった時点の重みを使うのが 早期終了(early stopping)です。

隠れ層に 100 個のニューロンを持つネットワークを、20 個のデータで学習させ、学習用と検証用の誤差を 100 回ごとに記録してみます。

import numpy as np

rng = np.random.default_rng(0)
x_tr = rng.uniform(-1, 1, (20, 1)); y_tr = np.sin(3 * x_tr) + rng.normal(0, 0.3, x_tr.shape)
x_va = rng.uniform(-1, 1, (200, 1)); y_va = np.sin(3 * x_va) + rng.normal(0, 0.3, x_va.shape)

# 隠れ層 100 個の大きめのネットワークを、20 個のデータで学習する
W1 = rng.normal(0, 1, (1, 100)); b1 = rng.normal(0, 1, 100)
W2 = rng.normal(0, 0.1, (100, 1)); b2 = np.zeros(1)

def predict(x):
    h = np.tanh(x @ W1 + b1)
    return h, h @ W2 + b2

lr, history = 0.05, []
for epoch in range(1, 20001):
    h, y = predict(x_tr)
    dy = 2 * (y - y_tr) / len(x_tr)
    dW2, db2 = h.T @ dy, dy.sum(0)
    dz = dy @ W2.T * (1 - h**2)
    dW1, db1 = x_tr.T @ dz, dz.sum(0)
    W1 -= lr * dW1; b1 -= lr * db1; W2 -= lr * dW2; b2 -= lr * db2
    if epoch % 100 == 0:
        tr = float(((predict(x_tr)[1] - y_tr) ** 2).mean())
        va = float(((predict(x_va)[1] - y_va) ** 2).mean())
        history.append((epoch, tr, va))

for epoch, tr, va in history:
    if epoch in [100, 1000, 3000, 10000, 20000]:
        print(epoch, round(tr, 4), round(va, 4))
best = min(history, key=lambda r: r[2])
print("検証用の誤差が最小", best[0], round(best[2], 4))

実行結果:

100 0.0419 0.1813
1000 0.0289 0.1299
3000 0.0265 0.1262
10000 0.0207 0.1296
20000 0.0196 0.1356
検証用の誤差が最小 4800 0.1255

左から、学習の回数、学習用の誤差、検証用の誤差です(ここでは 2 乗誤差の平均)。学習用の誤差は 20,000 回まで下がり続けますが、検証用の誤差は 4,800 回目の 0.1255 を底に、また上がり始めています。ここで止めた重みを使えば、それ以上の過学習を避けられます。

実際には、「検証用の誤差が、決めた回数(たとえば 10 エポック)続けて改善しなかったら止める」という形で使い、いちばん良かった時点の重みを保存しておきます。早期終了は、学習の回数で重みが大きくなるのを抑えているので、L2 正則化に近い働きをすることが知られています。

ドロップアウト:ニューロンをランダムに休ませる

学習中だけ出力を 0 にする

ドロップアウトは、ニューラルネットワークの学習中に、層の出力のうち一定の割合(たとえば半分)をランダムに 0 にする方法です。毎回違うニューロンが休むので、ネットワークは特定のニューロンに頼れなくなり、どのニューロンが欠けても答えられるような、偏りのない表し方を学びます。

ある層の 6 つの出力に、確率 0.5 でドロップアウトをかけてみます。

import numpy as np

rng = np.random.default_rng(0)
h = np.array([0.8, 0.1, 0.5, 0.9, 0.3, 0.7])   # ある層の出力
p = 0.5                                          # 消す確率
for _ in range(3):
    keep = rng.random(h.shape) >= p
    print(np.round(h * keep / (1 - p), 2))       # 残ったものを 1/(1-p) 倍して大きさをそろえる
outs = [(h * (rng.random(h.shape) >= p) / (1 - p)).sum() for _ in range(100000)]
print(round(float(h.sum()), 3), round(float(np.mean(outs)), 3))

実行結果:

[1.6 0.  0.  0.  0.6 1.4]
[1.6 0.2 1.  1.8 0.6 0. ]
[1.6 0.  1.  0.  0.6 1.4]
3.3 3.301

1〜3 行目は、ドロップアウトを 3 回かけた出力です。毎回違う場所が 0 になり、残った値は 1 ÷ (1 − 0.5) = 2 倍されています。2 倍するのは、半分が消えても出力の合計の期待値が変わらないようにするためです。最後の行で、元の出力の合計 3.3 と、ドロップアウトを 10 万回かけた出力の合計の平均 3.301 がほぼ一致していることを確かめています。こうしておけば、学習が終わって予測するときには、ドロップアウトを外してそのまま使えます。

ドロップアウトは、「少しずつ違う、たくさんのネットワークを学習させて平均する」のに近い効果があると説明されます。

データ拡張:データを水増しする

過学習はデータが少ないときに起きるので、データを増やせるなら、それがいちばん確実な対策です。画像では、学習用の画像を左右に反転したり、少し回転・拡大・切り抜きしたり、明るさや色を変えたりして、「同じ種類のものが写っている別の画像」を作り、学習用のデータを水増しします。これを データ拡張(data augmentation)と呼びます。

データよく使われる拡張
画像反転・切り抜き・回転・色の変更、2 枚の画像を混ぜる(mixup)、一部を隠す(Cutout)
音声速さや高さを変える・雑音を混ぜる
文章同じ意味の言い換え・翻訳して戻す

ただし、「その変換をしても答えが変わらない」ものだけを使う必要があります。数字の 6 を 180 度回すと 9 になってしまうように、問題によっては使えない変換もあります。

交差検証:調整の値を正しく選ぶ

テスト用のデータで選んではいけない

ここまでの例では、λ や学習の回数を、テスト用のデータでの誤差を見て比べました。しかし、実際にモデルを作るときに テスト用のデータを見ながら調整の値を選ぶと、テスト用のデータに合わせて過学習してしまい、テストでの成績が本当の実力より良く見えてしまいます。

そこで、データを 3 つに分けて使います。

データ使い道
学習用重みを学習する
検証用λ・次数・学習の回数などの調整の値(ハイパーパラメーター)を選ぶ
テスト用最後に 1 回だけ、本当の実力を測る

k 分割の交差検証

データが少ないと、検証用に取り分けるのももったいなくなります。そこで、学習用のデータを k 個に分け、そのうち 1 つを検証用、残りを学習用にして誤差を測ることを、検証用にする 1 つを入れ替えながら k 回くり返し、その平均で比べます。これを k 分割の交差検証と呼びます。

30 個のデータで、5 分割の交差検証を使って 9 次式の λ を選んでみます。多項式の L2 正則化のコードの続きです。

# 5 分割の交差検証で λ を選ぶ(テスト用のデータは使わない)
def cv_rmse(x, y, lam, k=5):
    idx = np.arange(len(x))
    errors = []
    for fold in range(k):
        val = idx % k == fold                     # 5 つに分けた 1 つを検証用に
        w = ridge(x[~val], y[~val], 9, lam)
        errors.append(rmse(w, x[val], y[val], 9))
    return float(np.mean(errors))

rng = np.random.default_rng(2)  # make_data が使う乱数を作り直す
x_cv, y_cv = make_data(30)
scores = {lam: cv_rmse(x_cv, y_cv, lam) for lam in [1e-9, 1e-7, 1e-5, 1e-3, 1e-1]}
print({lam: round(s, 3) for lam, s in scores.items()})
best = min(scores, key=scores.get)
w = ridge(x_cv, y_cv, 9, best)                    # 選んだ λ で全部のデータを使って学習し直す
print(best, round(rmse(w, x_test, y_test, 9), 3))

実行結果:

{1e-09: 0.245, 1e-07: 0.232, 1e-05: 0.205, 0.001: 0.227, 0.1: 0.343}
1e-05 0.225

1 行目が、λ ごとの交差検証の誤差の平均です。テスト用のデータを一度も見ずに、λ = 0.00001 がいちばん良いと選べました。2 行目は、選んだ λ で 30 個すべてを使って学習し直し、最後にテスト用のデータで測った誤差 0.225 です。

交差検証では、検証用のデータが学習用のデータと同じ性質を持っていることが前提です。時系列のデータで未来のデータを学習に混ぜてしまう、同じ人の写真が学習用と検証用の両方に入っている、といった場合は、検証での成績が実際より良く出てしまうので、分け方に注意が必要です。

深層学習と過学習

大きなモデルなのに過学習しにくい不思議

ここまでの考え方では、「データの数より重みの数が多いモデルは過学習する」はずです。ところが、今のニューラルネットワークは、学習用のデータの数よりはるかに多い重みを持ちながら、初めて見るデータでも高い性能を出しています。

2016 年の研究では、画像分類のネットワークが、正解のラベルをでたらめに付け替えた学習用のデータでさえ完全に覚えられることが示されました。つまり、ネットワークには丸暗記するだけの力があるのに、本物のデータではそうせずに一般的な規則を学んでいることになります。

また、モデルを大きくしていくと、テスト用の誤差がいったん上がったあと、ある大きさを超えると再び下がっていく 二重降下(double descent)という現象も報告されています。ちょうどデータをぴったり通れるくらいの大きさで誤差が最も悪くなり、それより大きいモデルでは、データを通る無数の解の中から、勾配降下法が「なめらかな」解を選ぶためだと考えられています。

なぜ大きなネットワークが過学習しにくいのかは、まだ完全には解明されていない研究のテーマです。実際の学習では、ここで紹介した L2 正則化(重み減衰)・ドロップアウト・データ拡張・早期終了を組み合わせて使うのがふつうです。

このサイトの ニューラルネットワークの学習の可視化 では、ノイズを加えたデータで大きなネットワークを学習させ、学習用とテスト用の誤差の差を見ながら、L1・L2 正則化の強さを変えたときの違いを確かめられます。

正則化の手法の流れ

この記事で紹介した手法を年代順に並べます。

年手法・出来事ポイント
1970リッジ回帰重みの 2 乗の和を足して、回帰の係数を安定させる
1974交差検証(ストーン)データを分けて入れ替えながら評価する
1992バイアスとバリアンスのジレンマニューラルネットワークの誤差を 2 つに分けて論じる
1996ラッソ重みの絶対値の和を足して、効いていない特徴の重みを 0 にする
1998早期終了の基準の整理検証用の誤差を見て学習を止める規則を比べる
2012データ拡張(AlexNet)画像の切り抜き・反転で ImageNet の過学習を抑える
2012〜2014ドロップアウト学習中にニューロンをランダムに休ませる
2016でたらめなラベルの丸暗記大きなネットワークは何でも覚えられることを示す
2017mixup・Cutout画像を混ぜる・一部を隠すデータ拡張
2019二重降下モデルを大きくすると、誤差が上がったあと再び下がる

まとめ

  • 過学習は、モデルが学習用のデータのノイズまで覚え、初めて見るデータで外れる状態。学習用とテスト用の誤差の差が大きいことで分かる
  • 過学習はモデルの複雑さとデータの量の釣り合いで決まり、データを増やすと減る
  • 誤差はバイアス(単純すぎて平均的にずれる)とバリアンス(複雑すぎてデータごとにぶれる)に分けられ、過学習はバリアンスが大きすぎる状態
  • L2 正則化は重みの 2 乗の和を損失に足して重みを小さく保ち、同じモデルでも複雑さを調整できる
  • L1 正則化は重みの絶対値の和を足し、効いていない重みをちょうど 0 にする
  • 早期終了は、検証用の誤差が上がり始めたところで学習を止める
  • ドロップアウトは学習中に出力をランダムに 0 にし、残った値を拡大して期待値をそろえる
  • データ拡張は、答えが変わらない変換でデータを水増しする
  • 調整の値は、テスト用のデータではなく、検証用のデータや k 分割の交差検証で選ぶ

参考

  • A. E. Hoerl and R. W. Kennard, "Ridge Regression: Biased Estimation for Nonorthogonal Problems", Technometrics 1970
  • M. Stone, "Cross-Validatory Choice and Assessment of Statistical Predictions", Journal of the Royal Statistical Society: Series B 1974
  • S. Geman et al., "Neural Networks and the Bias/Variance Dilemma", Neural Computation 1992
  • R. Tibshirani, "Regression Shrinkage and Selection via the Lasso", Journal of the Royal Statistical Society: Series B 1996
  • L. Prechelt, "Early Stopping — But When?", Neural Networks: Tricks of the Trade, Springer 1998
  • C. M. Bishop, "Pattern Recognition and Machine Learning", Springer 2006
  • A. Krizhevsky et al., "ImageNet Classification with Deep Convolutional Neural Networks", NeurIPS 2012
  • G. E. Hinton et al., "Improving neural networks by preventing co-adaptation of feature detectors", 2012(arXiv:1207.0580)
  • N. Srivastava et al., "Dropout: A Simple Way to Prevent Neural Networks from Overfitting", JMLR 2014
  • C. Zhang et al., "Understanding deep learning requires rethinking generalization", ICLR 2017(arXiv:1611.03530)
  • H. Zhang et al., "mixup: Beyond Empirical Risk Minimization", ICLR 2018(arXiv:1710.09412)
  • T. DeVries and G. W. Taylor, "Improved Regularization of Convolutional Neural Networks with Cutout", 2017(arXiv:1708.04552)
  • M. Belkin et al., "Reconciling modern machine-learning practice and the classical bias–variance trade-off", PNAS 2019(arXiv:1812.11118)
  • P. Nakkiran et al., "Deep Double Descent: Where Bigger Models and More Data Hurt", ICLR 2020(arXiv:1912.02292)