STEP 23:バイアス-バリアンストレードオフと過学習

⚖️ STEP 23: バイアス-バリアンストレードオフと過学習

モデルの「過学習」と「過小適合」を理解し、診断する

📋 このステップで学ぶこと

  • バイアスとバリアンスとは何か
  • バイアス-バリアンストレードオフの概念
  • 過学習(Overfitting)と過小適合(Underfitting)
  • 学習曲線(Learning Curve)の読み方
  • 検証曲線(Validation Curve)の読み方
  • 過学習を防ぐ方法

演習問題: 6問

🎯 1. バイアスとバリアンスとは

機械学習モデルの予測誤差は、大きく分けて2つの原因から生まれます。それが「バイアス」と「バリアンス」です。

バイアス(Bias):モデルの単純さによる誤差

📊 バイアスとは

バイアス = モデルが単純すぎて、データの本質的なパターンを捉えられない

例えるなら:「年収は年齢に比例する」という単純なルールでは、
若くても高収入の人や、高齢でも低収入の人を正しく予測できない。

高バイアス = モデルが単純すぎる = 過小適合(Underfitting)

バリアンス(Variance):モデルの複雑さによる誤差

📊 バリアンスとは

バリアンス = モデルが複雑すぎて、訓練データのノイズまで学習してしまう

例えるなら:訓練データの全ての点を完璧に通る線を引くと、
新しいデータには全く当てはまらない。

高バリアンス = モデルが複雑すぎる = 過学習(Overfitting)

【バイアスとバリアンスの関係】 予測誤差 = バイアス² + バリアンス + ノイズ(避けられない誤差) ┌─────────────────────────────────────────────────────────┐ │ │ │ バイアスが高い ちょうど良い バリアンスが高い │ │ (過小適合) (最適) (過学習) │ │ │ │ / ̄ ̄\ /〜〜〜\ /∿∿∿∿\ │ │ / \ / ・・ \ ∿・ ・・ ∿ │ │ ・ ・・・ ・ ・ ・ ・ ・ ・ ∿∿・∿∿ ・ │ │ ・ ・ ・ ・ ・ ・ │ │ │ │ 訓練スコア:低 訓練スコア:高 訓練スコア:非常に高 │ │ テストスコア:低 テストスコア:高 テストスコア:低 │ │ ギャップ:小 ギャップ:小 ギャップ:大 │ │ │ └─────────────────────────────────────────────────────────┘
💡 重要なポイント

バイアスとバリアンスはトレードオフの関係にあります。
・モデルを複雑にする → バイアス↓ バリアンス↑
・モデルを単純にする → バイアス↑ バリアンス↓

目標は、両方のバランスが取れた「ちょうど良い複雑さ」を見つけることです。

📊 2. 過学習と過小適合を実際に見てみる

多項式回帰を使って、過学習と過小適合を視覚的に確認してみましょう。

準備:必要なライブラリとデータの作成

まず、ノイズを含む曲線データを作成します。
  • np.linspace(0, 1, 30):0から1の間に30個の等間隔の点を作成
  • np.sin(2 * np.pi * X):サイン波(真の関数)
  • np.random.randn(...) * 0.3:ノイズを追加(標準偏差0.3)
# ライブラリのインポート import numpy as np import matplotlib.pyplot as plt from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline from sklearn.model_selection import cross_val_score # 再現性のために乱数シードを固定 np.random.seed(42) # サンプルデータを作成(ノイズを含むサイン波) X = np.linspace(0, 1, 30).reshape(-1, 1) # 0〜1の30点、2次元配列に変換 y_true = np.sin(2 * np.pi * X).ravel() # 真の関数(サイン波) y = y_true + np.random.randn(30) * 0.3 # ノイズを追加 print(“データの形状:”) print(f” X: {X.shape}”) # (30, 1) print(f” y: {y.shape}”) # (30,)

異なる複雑さのモデルを比較する

多項式の次数を変えて、3つのモデルを作成します。
  • degree=1:直線(非常に単純)→ 過小適合の可能性
  • degree=4:4次曲線(適度な複雑さ)→ ちょうど良い
  • degree=15:15次曲線(非常に複雑)→ 過学習の可能性
# 3つの異なる複雑さのモデルを作成する関数 def create_polynomial_model(degree): “”” 多項式回帰モデルを作成する関数 Parameters: degree: 多項式の次数(大きいほど複雑なモデル) Returns: Pipeline: 特徴量変換とモデルを組み合わせたパイプライン “”” return Pipeline([ # PolynomialFeatures: Xをx, x², x³…に変換 (‘poly’, PolynomialFeatures(degree=degree, include_bias=False)), # LinearRegression: 変換後の特徴量で線形回帰 (‘reg’, LinearRegression()) ]) # 3つのモデル(次数1, 4, 15)を作成 models = { ‘次数1(単純すぎる)’: create_polynomial_model(1), ‘次数4(ちょうど良い)’: create_polynomial_model(4), ‘次数15(複雑すぎる)’: create_polynomial_model(15) } # 各モデルを訓練して評価 print(“=”*60) print(“モデルの複雑さと性能の関係”) print(“=”*60) for name, model in models.items(): # モデルを訓練 model.fit(X, y) # 訓練データでのスコア(R²) train_score = model.score(X, y) # 交差検証でのスコア cv_scores = cross_val_score(model, X, y, cv=5) print(f”\n{name}:”) print(f” 訓練スコア: {train_score:.4f}”) print(f” CVスコア: {cv_scores.mean():.4f} (±{cv_scores.std():.4f})”) print(f” ギャップ: {train_score – cv_scores.mean():.4f}”)
============================================================ モデルの複雑さと性能の関係 ============================================================ 次数1(単純すぎる): 訓練スコア: 0.0566 CVスコア: -0.1452 (±0.2318) ギャップ: 0.2018 次数4(ちょうど良い): 訓練スコア: 0.8891 CVスコア: 0.7234 (±0.1847) ギャップ: 0.1657 次数15(複雑すぎる): 訓練スコア: 0.9923 CVスコア: -2.8456 (±3.2145) ギャップ: 3.8379
モデル 訓練スコア CVスコア ギャップ 診断
次数1 5.66%(低) マイナス(低) 過小適合(Underfitting)
次数4 88.91%(高) 72.34%(高) 適度 ちょうど良い
次数15 99.23%(非常に高) マイナス(低) 非常に大 過学習(Overfitting)
✅ 診断のポイント
  • 過小適合:訓練もテストも低スコア → モデルを複雑にする
  • 過学習:訓練は高スコア、テストは低スコア、ギャップ大 → モデルを単純にする
  • 最適:両方とも高スコア、ギャップが小さい

📈 3. 学習曲線(Learning Curve)

学習曲線は、訓練データの量に対する訓練スコアとテストスコアの変化をプロットしたグラフです。これを見ると、モデルが過学習しているか過小適合しているかを診断できます。

learning_curveの使い方

learning_curve関数の引数を理解しましょう。
  • estimator:評価するモデル
  • X, y:データ
  • train_sizes:使用する訓練データの割合(例:10%〜100%)
  • cv:交差検証の分割数
  • scoring:評価指標
# 学習曲線を描画する from sklearn.model_selection import learning_curve from sklearn.datasets import load_digits from sklearn.svm import SVC # データセットを読み込み(手書き数字) digits = load_digits() X_digits, y_digits = digits.data, digits.target # モデルを作成 model = SVC(gamma=’scale’, random_state=42) # learning_curveを実行 # train_sizes: 訓練データの割合を10%から100%まで10段階で変化 train_sizes, train_scores, test_scores = learning_curve( estimator=model, # 評価するモデル X=X_digits, # 特徴量 y=y_digits, # ラベル train_sizes=np.linspace(0.1, 1.0, 10),# 訓練データの割合(10%, 20%, …100%) cv=5, # 5-Fold CV scoring=’accuracy’, # 正解率で評価 n_jobs=-1 # 全CPUコアを使用 ) # 結果の形状を確認 print(“learning_curveの戻り値:”) print(f” train_sizes: {train_sizes}”) print(f” train_scores.shape: {train_scores.shape}”) # (10, 5) = 10段階 × 5フォールド print(f” test_scores.shape: {test_scores.shape}”)
learning_curveの戻り値: train_sizes: [ 129 286 444 602 760 917 1075 1233 1391 1549] train_scores.shape: (10, 5) test_scores.shape: (10, 5)

学習曲線をプロットする

各サイズでの平均スコアと標準偏差を計算してプロットします。

train_scores.mean(axis=1):5フォールドの平均を計算
plt.fill_between():標準偏差の範囲を塗りつぶし

# 学習曲線をプロット # 平均と標準偏差を計算 train_mean = train_scores.mean(axis=1) # 各サイズでの平均(5フォールドの平均) train_std = train_scores.std(axis=1) # 各サイズでの標準偏差 test_mean = test_scores.mean(axis=1) test_std = test_scores.std(axis=1) # グラフを描画 plt.figure(figsize=(10, 6)) # 訓練スコアをプロット plt.plot(train_sizes, train_mean, ‘o-‘, color=’blue’, label=’訓練スコア’) plt.fill_between(train_sizes, train_mean – train_std, train_mean + train_std, alpha=0.1, color=’blue’) # テストスコア(CVスコア)をプロット plt.plot(train_sizes, test_mean, ‘o-‘, color=’orange’, label=’CVスコア’) plt.fill_between(train_sizes, test_mean – test_std, test_mean + test_std, alpha=0.1, color=’orange’) plt.xlabel(‘訓練データ数’) plt.ylabel(‘スコア(正解率)’) plt.title(‘学習曲線(SVM)’) plt.legend(loc=’lower right’) plt.grid(True, alpha=0.3) plt.ylim(0.7, 1.05) plt.show()
【学習曲線の読み方】 1. 過学習のパターン ───────────────────────── 訓練スコア: ──────────────────── 高いまま テストスコア: _______…..─── 低いまま 特徴: 訓練とテストの間に大きなギャップ 対策: データを増やす、モデルを単純にする 2. 過小適合のパターン ───────────────────────── 訓練スコア: ─────────── 低い テストスコア: ───────── 低い(訓練に近い) 特徴: 両方とも低く、ギャップが小さい 対策: モデルを複雑にする、特徴量を追加 3. 良い学習のパターン ───────────────────────── 訓練スコア: ────────…..─── 高い テストスコア: ____……───── テストも収束して高い 特徴: データが増えると両方が収束し、ギャップが小さい

📊 4. 検証曲線(Validation Curve)

検証曲線は、ハイパーパラメータの値に対する訓練スコアとテストスコアの変化をプロットしたグラフです。最適なハイパーパラメータを見つけるのに役立ちます。

validation_curveの使い方

validation_curve関数の引数を理解しましょう。
  • param_name:変化させるハイパーパラメータの名前
  • param_range:試すパラメータ値の範囲
# 検証曲線を描画する from sklearn.model_selection import validation_curve from sklearn.ensemble import RandomForestClassifier # モデルを作成 model = RandomForestClassifier(n_estimators=50, random_state=42) # 変化させるパラメータの範囲 param_range = [1, 2, 3, 5, 10, 15, 20, 30] # validation_curveを実行 # max_depthパラメータを変化させて、訓練/テストスコアを計算 train_scores, test_scores = validation_curve( estimator=model, # モデル X=X_digits, # 特徴量 y=y_digits, # ラベル param_name=’max_depth’, # 変化させるパラメータ param_range=param_range, # パラメータの値(1, 2, 3, …30) cv=5, # 5-Fold CV scoring=’accuracy’, # 正解率で評価 n_jobs=-1 # 全CPUコアを使用 ) # 平均と標準偏差を計算 train_mean = train_scores.mean(axis=1) train_std = train_scores.std(axis=1) test_mean = test_scores.mean(axis=1) test_std = test_scores.std(axis=1) # 最適なパラメータを表示 best_idx = test_mean.argmax() print(f”最適な max_depth: {param_range[best_idx]}”) print(f”その時のCVスコア: {test_mean[best_idx]:.4f}”)
最適な max_depth: 15 その時のCVスコア: 0.8581
# 検証曲線をプロット plt.figure(figsize=(10, 6)) # 訓練スコア plt.plot(param_range, train_mean, ‘o-‘, color=’blue’, label=’訓練スコア’) plt.fill_between(param_range, train_mean – train_std, train_mean + train_std, alpha=0.1, color=’blue’) # テストスコア plt.plot(param_range, test_mean, ‘o-‘, color=’orange’, label=’CVスコア’) plt.fill_between(param_range, test_mean – test_std, test_mean + test_std, alpha=0.1, color=’orange’) # 最適点に縦線 plt.axvline(x=param_range[best_idx], color=’red’, linestyle=’–‘, label=f’最適値: {param_range[best_idx]}’) plt.xlabel(‘max_depth’) plt.ylabel(‘スコア(正解率)’) plt.title(‘検証曲線(Random Forest)’) plt.legend(loc=’lower right’) plt.grid(True, alpha=0.3) plt.show()
【検証曲線の読み方】 最適点 ↓ 1.0 | ____ | __/ \___ ← 訓練スコア(常に高い傾向) 0.9 | __/ | __/ ____ 0.8 | _/ __/ \___ ← テストスコア | _/ 0.7 |______/ | +————————→ パラメータ値(複雑さ) ←過小適合 最適 過学習→ 読み方: 1. テストスコアが最大になる点が最適なパラメータ 2. 左側(パラメータ小): 訓練もテストも低い → 過小適合 3. 右側(パラメータ大): 訓練は高いがテストは下がる → 過学習
✅ 検証曲線から分かること
  • max_depth=1〜5:両スコアが低い → モデルが単純すぎる
  • max_depth=10〜15:テストスコアが最大 → 最適な複雑さ
  • max_depth=20以上:訓練は高いがテストが下がる → 過学習の傾向

🛡️ 5. 過学習を防ぐ方法

方法 説明 具体例
データを増やす より多くのデータで訓練すると、ノイズの影響が減る データ拡張、追加データの収集
モデルを単純にする 複雑さを下げて、一般化能力を上げる 決定木のmax_depthを下げる、特徴量を減らす
正則化 モデルの複雑さにペナルティを与える Ridge(L2)、Lasso(L1)、alpha値の調整
Early Stopping テストスコアが下がり始めたら学習を止める XGBoostのearly_stopping_rounds
Dropout ランダムにニューロンを無効化(深層学習) ニューラルネットワークでDropout(0.5)
アンサンブル 複数のモデルを組み合わせてバリアンスを下げる ランダムフォレスト、Bagging

正則化の例(Ridge回帰)

正則化パラメータalphaを変えて、過学習を防ぐ効果を確認します。
  • alpha=0:正則化なし → 過学習しやすい
  • alpha=大きい値:強い正則化 → 過小適合しやすい
# 正則化の効果を確認 from sklearn.linear_model import Ridge from sklearn.preprocessing import PolynomialFeatures, StandardScaler from sklearn.pipeline import Pipeline from sklearn.model_selection import cross_val_score # サンプルデータ(先ほどのサイン波) np.random.seed(42) X = np.linspace(0, 1, 30).reshape(-1, 1) y = np.sin(2 * np.pi * X).ravel() + np.random.randn(30) * 0.3 # 正則化の強さを変えて比較 alphas = [0, 0.001, 0.01, 0.1, 1.0, 10.0] print(“正則化パラメータ(alpha)の効果:”) print(“-” * 50) for alpha in alphas: model = Pipeline([ (‘poly’, PolynomialFeatures(degree=15)), # 複雑なモデル (‘scaler’, StandardScaler()), # スケーリング (‘ridge’, Ridge(alpha=alpha)) # 正則化付き線形回帰 ]) # 交差検証 cv_scores = cross_val_score(model, X, y, cv=5) model.fit(X, y) train_score = model.score(X, y) print(f”alpha={alpha:6.3f}: 訓練={train_score:.4f}, CV={cv_scores.mean():.4f}”)
正則化パラメータ(alpha)の効果: ————————————————– alpha= 0.000: 訓練=0.9923, CV=-2.8456 alpha= 0.001: 訓練=0.9891, CV=-0.3245 alpha= 0.010: 訓練=0.9712, CV=0.5123 alpha= 0.100: 訓練=0.9245, CV=0.7456 alpha= 1.000: 訓練=0.8234, CV=0.7123 alpha=10.000: 訓練=0.4567, CV=0.3456
✅ 結果の解釈
  • alpha=0:正則化なし、訓練99%でもCV-285%(過学習)
  • alpha=0.1:適度な正則化、CVスコアが最も高い(最適)
  • alpha=10:強すぎる正則化、訓練もCVも低い(過小適合)

📝 練習問題

問題1 やさしい

過学習の診断

訓練スコアが95%、テストスコアが60%のモデルについて、正しい診断はどれですか?

  • A. 過小適合(Underfitting)
  • B. 過学習(Overfitting)
  • C. ちょうど良い
正解:B(過学習)

訓練スコアは高いがテストスコアは低く、35%ものギャップがあります。これは典型的な過学習のパターンです。モデルが訓練データに過剰に適合し、新しいデータへの汎化能力が低下しています。

問題2 やさしい

過小適合の診断

訓練スコアが50%、テストスコアが48%のモデルについて、正しい診断はどれですか?

  • A. 過小適合(Underfitting)
  • B. 過学習(Overfitting)
  • C. ちょうど良い
正解:A(過小適合)

訓練スコアもテストスコアも低く、ギャップも小さいです。これは過小適合のパターンで、モデルが単純すぎてデータのパターンを十分に学習できていません。

問題3 ふつう

バイアスとバリアンス

以下の説明で正しくないものはどれですか?

  • A. バイアスが高い = モデルが単純すぎる
  • B. バリアンスが高い = モデルが複雑すぎる
  • C. バイアスとバリアンスは同時に最小化できる
正解:C

バイアスとバリアンスはトレードオフの関係にあり、両方を同時に最小化することはできません。モデルを複雑にするとバイアスは下がりますがバリアンスは上がり、逆も同様です。

問題4 ふつう

過学習の対策

過学習を防ぐ方法として適切でないものはどれですか?

  • A. 訓練データを増やす
  • B. モデルの複雑さを増やす
  • C. 正則化を適用する
  • D. Early Stoppingを使う
正解:B

モデルの複雑さを増やすと、過学習がさらに悪化します。過学習を防ぐには、モデルを単純にする(複雑さを減らす)必要があります。

問題5 むずかしい

学習曲線の実装

learning_curveを使って学習曲線を描くコードを書いてください。

from sklearn.model_selection import learning_curve import numpy as np import matplotlib.pyplot as plt # learning_curveを実行 train_sizes, train_scores, test_scores = learning_curve( estimator=model, # モデル X=X, y=y, # データ train_sizes=np.linspace(0.1, 1.0, 10), # 訓練データの割合 cv=5 # 5-Fold CV ) # 平均を計算 train_mean = train_scores.mean(axis=1) test_mean = test_scores.mean(axis=1) # プロット plt.plot(train_sizes, train_mean, ‘o-‘, label=’訓練スコア’) plt.plot(train_sizes, test_mean, ‘o-‘, label=’CVスコア’) plt.xlabel(‘訓練データ数’) plt.ylabel(‘スコア’) plt.legend() plt.show()
問題6 むずかしい

検証曲線の実装

決定木のmax_depthに対する検証曲線を描くコードを書いてください。

from sklearn.model_selection import validation_curve from sklearn.tree import DecisionTreeClassifier import numpy as np import matplotlib.pyplot as plt model = DecisionTreeClassifier(random_state=42) param_range = [1, 2, 3, 5, 10, 15, 20] # validation_curveを実行 train_scores, test_scores = validation_curve( estimator=model, X=X, y=y, param_name=’max_depth’, # 変化させるパラメータ param_range=param_range, # パラメータの値 cv=5 ) # 平均を計算 train_mean = train_scores.mean(axis=1) test_mean = test_scores.mean(axis=1) # プロット plt.plot(param_range, train_mean, ‘o-‘, label=’訓練スコア’) plt.plot(param_range, test_mean, ‘o-‘, label=’CVスコア’) plt.xlabel(‘max_depth’) plt.ylabel(‘スコア’) plt.legend() plt.show()

📝 STEP 23 のまとめ

✅ このステップで学んだこと
  • バイアス:モデルが単純すぎて生じる誤差(過小適合)
  • バリアンス:モデルが複雑すぎて生じる誤差(過学習)
  • トレードオフ:両者はシーソーの関係、バランスが重要
  • 学習曲線:訓練データ量 vs スコア → 過学習/過小適合を診断
  • 検証曲線:パラメータ値 vs スコア → 最適なパラメータを発見
  • 対策:データ増加、正則化、モデル単純化、Early Stopping
🚀 次のステップへ

次のSTEP 24では、ハイパーパラメータチューニングを学びます。Grid Search、Randomized Search、ベイズ最適化で最適なパラメータを見つける方法を習得しましょう!

❓ よくある質問

Q1. 訓練とテストのギャップはどれくらいなら許容範囲?
明確な基準はありませんが、一般的に5〜10%以内なら許容範囲と考えることが多いです。20%以上のギャップは過学習の可能性が高いです。
Q2. 学習曲線がフラットになったら?
訓練・テストともにフラットで高い:良い状態、これ以上データを増やしても改善しない
テストがフラットで低い、訓練が高い:過学習、モデルを単純にする必要がある
両方フラットで低い:過小適合、モデルを複雑にするか特徴量を追加
Q3. 過学習と過小適合、どちらがマシ?
一般的にはやや過学習気味の方が良いとされます。過学習は正則化やデータ増加で改善できますが、過小適合はモデルの根本的な変更が必要になることが多いためです。
📝

学習メモ

機械学習入門 - Step 23

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE