青の統計学-DS Playground-

過学習と汎化

機械学習の究極の目標は、未知のデータに対して良い予測をすること、つまり汎化性能(Generalization Performance)を高めることです。しかし、訓練データに過度に適合してしまう過学習(Overfitting)は、この目標を阻む最大の障害です。本章では、過学習の理論的理解と実践的な対処法について学習します。

過学習とは何か

基本的な概念


[図2] 過学習例 過学習例


過学習とは、モデルが訓練データに過度に適合し、新しいデータに対する予測性能が低下する現象です。

例:試験対策のアナロジー

  • 良い学習:原理を理解し、応用問題も解ける
  • 過学習:過去問を暗記しただけ、初見問題は解けない

視覚的理解:多項式回帰の例

理想的なフィット:  y = 2x + 1 + ノイズ(シンプルな直線)
          過学習したモデル: 訓練データの全点を通る高次多項式(複雑な曲線)
          

過学習の数学的表現

期待リスク(汎化誤差): $R(f) = \mathbb{E}_{新しいデータ}[\text{損失}]$

経験リスク(訓練誤差): $\hat{R}(f) = \frac{1}{n}\sum_{i=1}^{n}\text{訓練データでの損失}$

過学習の状況: $\hat{R}(f) \ll R(f)$ (訓練誤差は小さいが、汎化誤差は大きい)

過学習の原因

1. モデルが複雑すぎる

  • パラメータ数がデータ数より多い
  • 高次の多項式を使用
  • 深すぎるニューラルネットワーク

2. 訓練データが少ない

  • データからパターンを学習するには不十分
  • ノイズの影響を受けやすい
  • 偶然的なパターンを真のパターンと誤認

3. 訓練の継続しすぎ

  • イテレーション数が多すぎる
  • 早期停止を行わない
  • 検証データでの監視を怠る

4. ノイズの学習

  • 真のパターンだけでなくノイズまで記憶
  • 訓練データ特有の偶然的なパターンを学習

バイアス-分散トレードオフ

関連教材(青の統計学)

基本的な分解

予測誤差は3つの要素に分解できます:

$\text{総誤差} = \text{バイアス}^2 + \text{分散} + \text{ノイズ}$

バイアス(Bias):

  • モデルの予測と真の値の系統的なずれ
  • 単純なモデル → 高バイアス

分散(Variance):

  • 異なる訓練データでの予測のばらつき
  • 複雑なモデル → 高分散

ノイズ:

  • 削減不可能な誤差
  • データ収集の限界など

直感的な理解:ダーツの例

高バイアス・低分散:

  • 的の中心からずれているが、毎回同じ場所に当たる
  • 単純なモデル(例:線形回帰で非線形データを予測)

低バイアス・高分散:

  • 平均は的の中心だが、毎回大きくばらつく
  • 複雑なモデル(例:高次多項式)

理想(低バイアス・低分散):

  • 的の中心に集中して当たる
  • 適切な複雑さのモデル

モデル複雑度とエラーの関係

モデル複雑度 →  単純        適切        複雑
          バイアス     →  高    ↘    中    ↘    低
          分散         →  低    ↗    中    ↗    高
          総誤差       →  高    ↘    最小   ↗    高
          

過学習の対策

関連教材(青の統計学)

1. データを増やす

最も効果的な方法

  • より多くのデータでパターンを正確に学習
  • ノイズの影響を相対的に減少

データ拡張の例:

# 画像データの場合
          from tensorflow.keras.preprocessing.image import ImageDataGenerator

          datagen = ImageDataGenerator(
              rotation_range=20,      # 回転
              width_shift_range=0.1,  # 水平移動
              height_shift_range=0.1, # 垂直移動
              horizontal_flip=True    # 水平反転
          )
          

2. モデルを単純化

パラメータ数を減らす

  • 少ない特徴量を使用
  • 浅いネットワーク
  • 低次の多項式

3. 正則化(Regularization)


[図4] 正則化効果 正則化効果


正則化の目的は、訓練データに対して過度に複雑な当てはめをしないよう制御することです。
損失を下げるだけならパラメータを大きくしてでも当てにいけますが、それはしばしばノイズまで学習してしまいます。
そこで「大きすぎるパラメータには罰則を与える」項を目的関数に足し、汎化しやすい解へ誘導します。

L2正則化(Ridge): $\text{目的関数} = \text{損失} + \lambda \sum_{i} w_i^2$

L2は全パラメータをなだらかに小さくする働きがあり、特定の特徴量に重みが集中しすぎるのを防ぎます。
分かりやすく言うと「モデル全体にブレーキをかける」イメージです。

L1正則化(Lasso): $\text{目的関数} = \text{損失} + \lambda \sum_{i} |w_i|$

L1は一部の重みを厳密に0へ押しやすく、特徴量選択の効果を持ちます。
つまり「重要でない入力を自動的に落とす」性質があり、解釈性向上にも有効です。

L1とL2の使い分け(実務の目安):

  • 特徴量が多く、不要特徴を絞りたい:L1寄り
  • 予測の安定性を優先したい:L2寄り
  • 両方ほしい:Elastic Net(L1+L2)

効果:

  • パラメータの大きさを制限
  • よりスムーズなモデルを学習

4. 早期停止(Early Stopping)

概念: 検証誤差が増加し始めたら訓練を停止

# 早期停止の実装例
          best_val_loss = float('inf')
          patience = 5
          patience_counter = 0

          for epoch in range(max_epochs):
              # 訓練とバリデーション
              train_model()
              val_loss = validate_model()
              
              if val_loss < best_val_loss:
                  best_val_loss = val_loss
                  save_model()  # 最良のモデルを保存
                  patience_counter = 0
              else:
                  patience_counter += 1
                  
              if patience_counter >= patience:
                  print(f"早期停止: エポック {epoch}")
                  break

          load_best_model()  # 最良のモデルを復元
          

学習曲線による診断

学習曲線の見方

縦軸: 誤差(損失) 横軸: 訓練イテレーション数(エポック) 2つの線: 訓練誤差、検証誤差

パターンによる診断

1. 理想的なケース:

訓練誤差 ↘ ↘ ↘ → 低い値で安定
          検証誤差 ↘ ↘ ↘ → 低い値で安定(訓練誤差と近い)
          

2. 過学習のケース:

訓練誤差 ↘ ↘ ↘ → 非常に低い値
          検証誤差 ↘ ↗ ↗ → 途中から上昇(訓練誤差と大きな差)
          

3. 未学習のケース:

訓練誤差 → 高い値で停滞
          検証誤差 → 高い値で停滞(両方とも改善しない)
          

コラム:実践的な過学習対策

ドロップアウト(Dropout)

ニューラルネットワークで使用される正則化手法:

import tensorflow as tf

          model = tf.keras.Sequential([
              tf.keras.layers.Dense(128, activation='relu'),
              tf.keras.layers.Dropout(0.5),  # 50%のユニットをランダムに無効化
              tf.keras.layers.Dense(64, activation='relu'),
              tf.keras.layers.Dropout(0.3),  # 30%のユニットをランダムに無効化
              tf.keras.layers.Dense(10, activation='softmax')
          ])
          

効果:

  • 特定のニューロンへの依存を防ぐ
  • アンサンブル効果による汎化性能向上

アンサンブル学習

複数のモデルを組み合わせて予測:

from sklearn.ensemble import RandomForestClassifier, VotingClassifier
          from sklearn.linear_model import LogisticRegression
          from sklearn.svm import SVC

          # 異なるアルゴリズムを組み合わせ
          clf1 = LogisticRegression()
          clf2 = RandomForestClassifier()
          clf3 = SVC(probability=True)

          # 投票による分類
          voting_clf = VotingClassifier(
              estimators=[('lr', clf1), ('rf', clf2), ('svc', clf3)],
              voting='soft'  # 確率の平均
          )

          voting_clf.fit(X_train, y_train)
          predictions = voting_clf.predict(X_test)
          

利点:

  • 個々のモデルの過学習を相殺
  • 安定した予測性能

コラム:ハイパーパラメータチューニング

過学習を防ぐには適切なハイパーパラメータの設定が重要です。

グリッドサーチ

from sklearn.model_selection import GridSearchCV
          from sklearn.ensemble import RandomForestClassifier

          # パラメータの候補を定義
          param_grid = {
              'n_estimators': [50, 100, 200],      # 木の数
              'max_depth': [3, 5, 7, None],        # 最大深度
              'min_samples_split': [2, 5, 10],     # 分割に必要な最小サンプル数
              'min_samples_leaf': [1, 2, 4]        # 葉に必要な最小サンプル数
          }

          # グリッドサーチで最適化
          rf = RandomForestClassifier(random_state=42)
          grid_search = GridSearchCV(
              rf, param_grid, cv=5, scoring='accuracy', n_jobs=-1
          )

          grid_search.fit(X_train, y_train)
          best_params = grid_search.best_params_
          best_model = grid_search.best_estimator_

          print(f"最適パラメータ: {best_params}")
          print(f"最高スコア: {grid_search.best_score_:.3f}")
          

ランダムサーチ

from sklearn.model_selection import RandomizedSearchCV
          from scipy.stats import randint, uniform

          # パラメータの分布を定義
          param_dist = {
              'n_estimators': randint(50, 200),
              'max_depth': [3, 5, 7, 10, None],
              'min_samples_split': randint(2, 11),
              'min_samples_leaf': randint(1, 5),
              'max_features': uniform(0.1, 0.9)
          }

          # ランダムサーチ(グリッドサーチより効率的)
          random_search = RandomizedSearchCV(
              rf, param_dist, n_iter=100, cv=5, 
              scoring='accuracy', n_jobs=-1, random_state=42
          )

          random_search.fit(X_train, y_train)
          

実践的なワークフロー

過学習対策の優先順位

  1. データを増やす(最も効果的)

    • より多くのデータを収集
    • データ拡張技術の活用
  2. 適切なモデル複雑度の選択

    • 単純なモデルから開始
    • 段階的に複雑化
  3. 正則化の適用

    • L1/L2正則化
    • ドロップアウト
  4. 早期停止の実装

    • 検証誤差の監視
    • 適切なタイミングでの停止
  5. アンサンブル手法

    • 複数モデルの組み合わせ
    • 予測の安定化

総合的な実装例

import numpy as np
          from sklearn.model_selection import train_test_split, cross_val_score
          from sklearn.preprocessing import StandardScaler
          from sklearn.linear_model import Ridge
          from sklearn.ensemble import RandomForestClassifier
          from sklearn.metrics import classification_report
          import matplotlib.pyplot as plt

          def prevent_overfitting_workflow(X, y, problem_type='classification'):
              """
              過学習を防ぐ総合的なワークフロー
              """
              # 1. データ分割
              X_train, X_test, y_train, y_test = train_test_split(
                  X, y, test_size=0.2, stratify=y, random_state=42
              )
              
              # 2. 前処理
              scaler = StandardScaler()
              X_train_scaled = scaler.fit_transform(X_train)
              X_test_scaled = scaler.transform(X_test)
              
              # 3. モデル選択(複雑度を制御)
              if problem_type == 'regression':
                  models = {
                      'Ridge(alpha=0.1)': Ridge(alpha=0.1),
                      'Ridge(alpha=1.0)': Ridge(alpha=1.0),
                      'Ridge(alpha=10.0)': Ridge(alpha=10.0)
                  }
              else:
                  models = {
                      'RF(max_depth=3)': RandomForestClassifier(max_depth=3, random_state=42),
                      'RF(max_depth=5)': RandomForestClassifier(max_depth=5, random_state=42),
                      'RF(max_depth=None)': RandomForestClassifier(max_depth=None, random_state=42)
                  }
              
              # 4. 交差検証で評価
              results = {}
              for name, model in models.items():
                  scores = cross_val_score(model, X_train_scaled, y_train, cv=5)
                  results[name] = {
                      'mean': scores.mean(),
                      'std': scores.std()
                  }
                  print(f"{name}: {scores.mean():.3f} ± {scores.std():.3f}")
              
              # 5. 最良モデルを選択してテスト
              best_model_name = max(results, key=lambda x: results[x]['mean'])
              best_model = models[best_model_name]
              
              best_model.fit(X_train_scaled, y_train)
              test_score = best_model.score(X_test_scaled, y_test)
              
              print(f"\n最良モデル: {best_model_name}")
              print(f"テストスコア: {test_score:.3f}")
              
              return best_model, scaler

          # 使用例(分類問題の場合)
          # best_model, scaler = prevent_overfitting_workflow(X, y, 'classification')
          

まとめ

過学習は機械学習における最も重要な課題の一つです。重要なポイント:

  1. 概念の理解:訓練データへの過度な適合と汎化性能の低下
  2. バイアス-分散トレードオフ:モデル複雑度とエラーの関係
  3. 対策の階層:データ増加 → モデル単純化 → 正則化 → 早期停止
  4. 診断方法:学習曲線による過学習・未学習の検出
  5. 実践手法:ドロップアウト、アンサンブル、ハイパーパラメータチューニング

これらの知識を活用することで、実用的で汎化性能の高いモデルを構築できるようになります。

これでStage 1の基礎概念の学習は完了です。次のStageでは、具体的な機械学習アルゴリズムについて詳しく学習していきます。