scikit-learn SGDとは?初心者向け確率的勾配降下法の徹底解説

2025-05-27

確率的勾配降下法(SGD)とは?

機械学習モデルの学習では、多くの場合、予測と実際の値との間の誤差(損失)を最小化することが目標となります。この損失を最小化するために、モデルのパラメータ(重み)を調整していく過程を「最適化」と呼びます。勾配降下法は、この最適化のための代表的なアルゴリズムの一つです。

従来の勾配降下法(バッチ勾配降下法)では、全ての訓練データを使って損失関数の勾配(傾き)を計算し、それに基づいてパラメータを更新します。これは、データセットが大きい場合に計算コストが非常に高くなるという問題があります。

一方、確率的勾配降下法(SGD)は、この問題を解決するために考案されました。SGDでは、訓練データの中からランダムに1つのサンプル(または少数のサンプル、これをミニバッチと呼びます)を選び出し、そのサンプルに対する損失関数の勾配を計算してパラメータを更新します。 これを繰り返すことで、効率的に損失を最小化していきます。

scikit-learnでのSGD

scikit-learnでは、SGDClassifier(分類問題用)とSGDRegressor(回帰問題用)というクラスがSGDアルゴリズムを実装しています。これらは、正則化された線形モデルを確率的勾配降下法を用いて学習するために使用されます。

SGDClassifier / SGDRegressor の特徴

  1. 効率性(大規模データへの対応):

    • SGDは、一度に全てのデータを使うのではなく、一部のデータでパラメータを更新するため、大規模なデータセットやオンライン学習(データが逐次的に到着する状況)に適しています。
    • partial_fit メソッドを使用することで、データをバッチごとに学習させる「オンライン学習」や「アウトオブコア学習」(メモリに収まらないデータに対する学習)も可能です。
  2. 多様な線形モデル:

    • SGDはあくまで最適化アルゴリズムであり、特定のモデルの種類を指すわけではありません。SGDClassifierSGDRegressor は、loss パラメータを変更することで、さまざまな線形モデル(例:線形SVM、ロジスティック回帰、パーセプトロンなど)として機能させることができます。
      • loss='hinge':線形SVM(デフォルト)
      • loss='log_loss':ロジスティック回帰
      • loss='squared_error':線形回帰(SGDRegressorのデフォルト)
      • 他にもHuber、Epsilon-insensitiveなどの損失関数が選択可能です。
  3. 正則化:

    • 過学習を防ぐための正則化(L1、L2、Elastic Net)を適用できます。penalty パラメータで指定します。
  4. ハイパーパラメータの調整:

    • 学習率(learning_rateeta0など)、エポック数(max_iter)、正則化の強さ(alpha)、早期停止(early_stopping)など、多くのハイパーパラメータを調整することで、モデルの性能を最適化できます。
  5. データスケーリングの重要性:

    • SGDは特徴量のスケールに敏感です。そのため、データを学習前に標準化(StandardScalerなど)することが強く推奨されます。

使用例(概念)

from sklearn.linear_model import SGDClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# ダミーデータの生成
X, y = make_classification(n_samples=10000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# パイプラインの作成(StandardScalerでデータを標準化し、SGDClassifierを適用)
# loss='log_loss' でロジスティック回帰として学習
model = make_pipeline(StandardScaler(), SGDClassifier(loss='log_loss', max_iter=1000, tol=1e-3, random_state=42))

# モデルの学習
model.fit(X_train, y_train)

# 予測と評価
accuracy = model.score(X_test, y_test)
print(f"モデルの精度: {accuracy:.4f}")

利点:

  • オンライン学習: データが逐次的に利用可能になる場合に特に有効。
  • メモリ効率: 一度に全てのデータをメモリに読み込む必要がないため、メモリ消費量が少ない。
  • 高速性: 大規模データセットに対して非常に高速に学習できる。

欠点:

  • スケーリングの必要性: ほとんどの場合、特徴量のスケーリングが必須。
  • 収束の不安定性: 勾配が確率的に計算されるため、損失関数の収束がバッチ勾配降下法に比べて不安定になることがある(振動しながら収束する)。


データのスケーリングを怠る

これはSGDを使う上で最も一般的な間違いであり、最も重要なトラブルシューティングポイントです。

問題: SGDは、特徴量のスケール(値の範囲)に非常に敏感です。スケールが大きく異なる特徴量がある場合、勾配が非常に大きくなったり小さくなったりして、最適化が不安定になったり、適切に収束しなかったりします。結果として、モデルの精度が非常に低くなったり、学習が進まなかったりします。

解決策: データをモデルに投入する前に、必ずスケーリング(標準化または正規化)を行ってください。

  • 正規化 (Normalization): 各特徴量の値を特定の範囲(例: [0, 1] または [-1, 1])にスケーリングします。
    from sklearn.preprocessing import MinMaxScaler
    # ...
    model = make_pipeline(MinMaxScaler(), SGDClassifier(random_state=42))
    
  • 標準化 (Standardization): 各特徴量の平均を0、標準偏差を1に変換します。これが最も一般的で推奨される方法です。
    from sklearn.preprocessing import StandardScaler
    from sklearn.pipeline import make_pipeline
    from sklearn.linear_model import SGDClassifier
    
    # データはX_train, y_trainとする
    model = make_pipeline(StandardScaler(), SGDClassifier(random_state=42))
    model.fit(X_train, y_train)
    

特にStandardScalerは、多くの状況で良い結果をもたらします。

モデルの収束が不十分、または収束しない

問題:

  • tol (停止基準) が厳しすぎる、または緩すぎる:適切な収束点で停止しない。
  • eta0 (初期学習率) が不適切:
    • 大きすぎる場合:損失が発散したり、最適解の周りで大きく振動して収束しない(オーバーシュート)。
    • 小さすぎる場合:学習が非常に遅く、max_iterに達しても十分に収束しない。
  • max_iter (エポック数) が少なすぎる:十分な学習が行われず、損失が十分に小さくならない。

解決策:

  • 損失関数の選択: 選択した損失関数が問題に適しているか確認します。

    • 分類問題:
      • loss='hinge' (デフォルト): 線形SVMに相当
      • loss='log_loss' (旧log): ロジスティック回帰に相当。予測確率が必要な場合に適しています。
      • loss='modified_huber': ヒンジ損失とロジスティック損失のハイブリッドで、外れ値に強い。
    • 回帰問題:
      • loss='squared_error' (デフォルト): 最小二乗法に相当
      • loss='huber': 外れ値に強い
      • loss='epsilon_insensitive': SVRに相当
  • tol (停止基準) の調整: tol は、損失が改善しなくなったと判断する閾値です。デフォルトは1e-3ですが、より厳しくしたい場合は小さく、より早く停止したい場合は大きく設定します。

    model = SGDClassifier(max_iter=1000, tol=1e-4, random_state=42)
    
  • eta0 (学習率) の調整: learning_rate='constant'learning_rate='invscaling' と共に eta0 の値をグリッドサーチやランダムサーチで探索します。

    • eta0 の候補として、10−3,10−2,10−1 など、対数スケールで試すと良いでしょう。
    • learning_rate='optimal' は、理論的には最適な学習率を自動的に選択しようとしますが、初期のeta0と比べてパフォーマンスが劣る場合もあります。
  • early_stopping=True の利用: early_stopping=True を設定すると、自動的に検証セットのスコアに基づいて学習を早期に停止します。これにより、過学習を防ぎつつ、適切なエポック数で学習を終えることができます。validation_fractionで検証セットの割合を指定できます。

    model = SGDClassifier(early_stopping=True, validation_fraction=0.1, max_iter=1000, random_state=42)
    
  • max_iter の調整: 経験的に、106程度の訓練サンプルを観測すると収束すると言われています。もしデータセットのサイズがNであれば、max_iter = ceil(10**6 / N) を初期値として試すことができます。しかし、実際にはこれより少ないエポック数で収束することも多いです。

    # 例: データサイズが1000の場合
    # max_iter = int(np.ceil(10**6 / 1000))  # -> 1000
    model = SGDClassifier(max_iter=1000, random_state=42)
    

モデルの過学習または未学習

問題:

  • 過学習 (Overfitting): 訓練データのスコアは高いが、テストデータのスコアが低い場合。モデルが訓練データに過度に適合し、未知のデータに対する汎化性能が低い状態。
    • 原因: max_iterが多すぎる、学習率が大きすぎる、正則化が弱すぎる、特徴量が多すぎる、データが少ない。
  • 未学習 (Underfitting): 訓練データとテストデータの両方でスコアが低い場合。モデルがデータを十分に学習できていない状態。
    • 原因: max_iterが少なすぎる、学習率が小さすぎる、正則化が強すぎる、特徴量が不足している、モデルが単純すぎる。

解決策:

  • データ量の確認: データ量が少ない場合、モデルの学習が不安定になりやすく、過学習のリスクが高まります。
  • 特徴量エンジニアリング: 適切な特徴量を追加または削除することで、モデルの表現力を調整します。
  • early_stopping=True: 前述の通り、早期停止は過学習を防ぐ効果もあります。
  • 正則化 (penalty, alpha, l1_ratio) の調整:
    • penalty: 'l1' (Lasso)、'l2' (Ridge)、'elasticnet' (L1とL2の組み合わせ) を選択できます。
    • alpha: 正則化の強さを制御します。値が大きいほど正則化が強く、過学習を抑制します。最適なalphaを見つけるために、10−6から100の範囲で対数スケールで探索することが一般的です。
    • l1_ratio (Elastic Netの場合): L1とL2正則化の混合比を制御します。0でL2のみ、1でL1のみになります。
    # L2正則化を試す
    model = SGDClassifier(penalty='l2', alpha=0.001, random_state=42)
    # Elastic Net正則化を試す
    model = SGDClassifier(penalty='elasticnet', alpha=0.001, l1_ratio=0.5, random_state=42)
    

決定論的な結果が得られない

問題: SGDは確率的なアルゴリズムであるため、毎回異なる訓練データサンプル(またはミニバッチ)が使用されるため、random_stateを設定しないと、コードを実行するたびに結果が異なることがあります。

解決策:

  • random_state の設定: SGDClassifier または SGDRegressor のインスタンス化時に random_state パラメータに任意の整数値を設定します。これにより、同じ入力データに対して常に同じ結果が得られるようになります。
    model = SGDClassifier(random_state=42) # 任意の整数値
    

partial_fit の誤った使用

問題: partial_fit はオンライン学習やアウトオブコア学習に非常に便利ですが、以下のような間違いが起こりがちです。

  • データのスケーリングを各バッチごとに行う(パイプラインを使わない場合)。
  • 初回呼び出し時に全てのクラス(分類問題の場合)を指定しない。

解決策:

  • パイプラインの使用: partial_fitとスケーラーを組み合わせる場合、パイプラインを使用すると、スケーリングを適切に適用しつつ、partial_fitを呼び出すのが簡単になります。ただし、StandardScalerなどの変換器はpartial_fitメソッドを持たないため、パイプラインの最後のステップでpartial_fitを呼び出す必要があります。
  • 初回呼び出し時のクラス指定: partial_fitを最初に呼び出す際に、すべての既知のクラスラベルをclasses=引数に渡す必要があります。これは、モデルがどのクラスを予測するべきかを事前に知る必要があるためです。
    from sklearn.linear_model import SGDClassifier
    from sklearn.preprocessing import StandardScaler
    import numpy as np
    
    scaler = StandardScaler()
    sgd = SGDClassifier(random_state=42)
    
    # データをバッチに分割 (例)
    X_batches = [X[:50], X[50:]]
    y_batches = [y[:50], y[50:]]
    
    # 全てのクラスを初回に指定
    # y_train_all_unique_classes は訓練データに含まれる全てのユニークなクラス
    sgd.partial_fit(scaler.fit_transform(X_batches[0]), y_batches[0], classes=np.unique(y_train_all_unique_classes))
    
    # 2回目以降はclasses指定不要
    sgd.partial_fit(scaler.transform(X_batches[1]), y_batches[1])
    

データセットが小さすぎる

問題: SGDは大規模データセット向けに設計されたアルゴリズムです。データセットが非常に小さい場合(例えば数百件以下)、バッチ勾配降下法やL-BFGSなどの他の最適化アルゴリズムの方が安定して優れた性能を発揮する可能性があります。SGDは、ランダムに選択された単一サンプル(またはミニバッチ)の勾配を使用するため、データが少ないと勾配推定のノイズが大きくなり、収束が不安定になります。

解決策:

  • データセットが小さい場合は、LogisticRegression(ロジスティック回帰)、SVC(サポートベクターマシン)、または他のモデルのデフォルトのソルバー(通常はL-BFGSなど)を試すことを検討してください。これらのモデルは、小さいデータセットでも安定した学習が期待できます。

問題: SGDは調整すべきハイパーパラメータが多い(loss, penalty, alpha, l1_ratio, eta0, learning_rate, max_iter, tolなど)ため、適切な組み合わせを見つけるのが難しいことがあります。デフォルト値が常に最適とは限りません。

解決策:

  • グリッドサーチ (GridSearchCV) またはランダムサーチ (RandomizedSearchCV): 最適なハイパーパラメータの組み合わせを体系的に探索するために、これらのツールを積極的に利用してください。
    from sklearn.model_selection import GridSearchCV
    from sklearn.pipeline import Pipeline
    
    pipeline = Pipeline([
        ('scaler', StandardScaler()),
        ('sgd', SGDClassifier(random_state=42))
    ])
    
    param_grid = {
        'sgd__loss': ['hinge', 'log_loss'],
        'sgd__alpha': [1e-4, 1e-3, 1e-2],
        'sgd__max_iter': [500, 1000, 2000]
    }
    
    grid_search = GridSearchCV(pipeline, param_grid, cv=5, n_jobs=-1)
    grid_search.fit(X_train, y_train)
    
    print(f"Best parameters: {grid_search.best_params_}")
    print(f"Best score: {grid_search.best_score_}")
    


scikit-learnにおける確率的勾配降下法 (SGD) のプログラミング例

確率的勾配降下法 (SGD) は、大規模データセットでの学習に非常に効率的なアルゴリズムです。scikit-learnでは、SGDClassifierが分類タスクに、SGDRegressorが回帰タスクに利用されます。

準備: 必要なライブラリのインポート

まず、必要なライブラリをインポートします。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification, make_regression # ダミーデータ生成用
from sklearn.model_selection import train_test_split, GridSearchCV # データ分割、ハイパーパラメータチューニング
from sklearn.preprocessing import StandardScaler # データスケーリング
from sklearn.linear_model import SGDClassifier, SGDRegressor # SGDモデル
from sklearn.metrics import accuracy_score, mean_squared_error, r2_score # 評価指標
from sklearn.pipeline import Pipeline # パイプライン

SGDClassifier (分類問題) の例

分類タスクにSGDClassifierを使用します。

基本的な使い方 (標準化込み)

最も基本的な使用例です。データの標準化はSGDにとって非常に重要なので、パイプラインを使って組み込むことを強く推奨します。

# 1. ダミーデータの生成 (二値分類)
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 2. モデルの構築と学習
# パイプライン: StandardScalerでデータを標準化し、SGDClassifierを適用
# loss='log_loss': ロジスティック回帰として学習
# max_iter: 最大エポック数 (収束しない場合があるため十分に大きく設定)
# tol: 停止基準 (損失がこれ以上改善しない場合に停止)
# random_state: 結果を再現可能にするため
print("--- SGDClassifier: 基本的な使い方 ---")
pipeline_clf = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_clf', SGDClassifier(loss='log_loss', max_iter=1000, tol=1e-3, random_state=42))
])

pipeline_clf.fit(X_train, y_train)

# 3. 予測と評価
y_pred_clf = pipeline_clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred_clf)
print(f"テストデータの精度: {accuracy:.4f}")

# 予測確率の取得 (log_lossを指定した場合のみ可能)
if pipeline_clf.named_steps['sgd_clf'].loss == 'log_loss':
    y_proba_clf = pipeline_clf.predict_proba(X_test)
    print(f"最初の5つの予測確率 (クラス0, クラス1): {y_proba_clf[:5]}")

解説:

  • SGDClassifier: 確率的勾配降下法による分類器。
    • loss='log_loss': ロジスティック回帰の損失関数を使用します。これにより、predict_probaメソッドで予測確率を得ることができます。
    • max_iter: 最大イテレーション(エポック)数を指定します。学習がこれ以上進まなくても、この回数に達したら学習を終了します。
    • tol: 収束のための許容誤差。指定された回数のイテレーションで損失がtolよりも改善しなくなった場合、学習を早期に停止します。
    • random_state: 乱数のシードを設定することで、同じコードを実行したときに常に同じ結果が得られるようにします。
  • StandardScaler: 各特徴量の平均を0、標準偏差を1にスケーリングします。SGDには必須です。
  • Pipeline: 複数の処理ステップ(ここでは標準化とモデル学習)を結合し、コードを簡潔にします。
  • train_test_split: データを訓練用とテスト用に分割します。
  • make_classification: 1000個のサンプルと20個の特徴量を持つ二値分類用のダミーデータを生成します。

異なる損失関数の選択

SGDClassifierは、lossパラメータを変更することで、さまざまな線形モデルとして機能します。

print("\n--- SGDClassifier: 異なる損失関数の選択 ---")
# 線形SVM (デフォルト)
pipeline_svm = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_svm', SGDClassifier(loss='hinge', max_iter=1000, tol=1e-3, random_state=42))
])
pipeline_svm.fit(X_train, y_train)
accuracy_svm = accuracy_score(y_test, pipeline_svm.predict(X_test))
print(f"線形SVM (hinge loss) の精度: {accuracy_svm:.4f}")

# パーセプトロン (Perceptron)
# loss='perceptron' は学習率が自動的に調整されるため、eta0の指定は不要です。
pipeline_perceptron = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_perceptron', SGDClassifier(loss='perceptron', eta0=1, learning_rate='constant', max_iter=1000, tol=1e-3, random_state=42))
])
pipeline_perceptron.fit(X_train, y_train)
accuracy_perceptron = accuracy_score(y_test, pipeline_perceptron.predict(X_test))
print(f"パーセプトロン (perceptron loss) の精度: {accuracy_perceptron:.4f}")

解説:

  • loss='perceptron': パーセプトロンに対応します。非常にシンプルな線形モデルで、確率的な出力は得られません。eta0(学習率)とlearning_rateの設定が重要になりますが、パーセプトロンの学習ルールに従うため、通常は特定の値を設定します。
  • loss='hinge': 線形サポートベクターマシン (SVM) に対応します。これがSGDClassifierのデフォルトです。

正則化 (Regularization) の利用

過学習を防ぐために正則化を適用できます。penaltyalphaで制御します。

print("\n--- SGDClassifier: 正則化の利用 ---")
# L2正則化 (Ridge)
pipeline_l2 = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_l2', SGDClassifier(loss='log_loss', penalty='l2', alpha=0.0001, max_iter=1000, tol=1e-3, random_state=42))
])
pipeline_l2.fit(X_train, y_train)
accuracy_l2 = accuracy_score(y_test, pipeline_l2.predict(X_test))
print(f"L2正則化 (alpha=0.0001) の精度: {accuracy_l2:.4f}")

# L1正則化 (Lasso) - 特徴量選択効果あり
pipeline_l1 = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_l1', SGDClassifier(loss='log_loss', penalty='l1', alpha=0.0001, max_iter=1000, tol=1e-3, random_state=42))
])
pipeline_l1.fit(X_train, y_train)
accuracy_l1 = accuracy_score(y_test, pipeline_l1.predict(X_test))
print(f"L1正則化 (alpha=0.0001) の精度: {accuracy_l1:.4f}")

# Elastic Net正則化 (L1とL2の組み合わせ)
pipeline_elastic = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_elastic', SGDClassifier(loss='log_loss', penalty='elasticnet', alpha=0.0001, l1_ratio=0.5, max_iter=1000, tol=1e-3, random_state=42))
])
pipeline_elastic.fit(X_train, y_train)
accuracy_elastic = accuracy_score(y_test, pipeline_elastic.predict(X_test))
print(f"Elastic Net正則化 (alpha=0.0001, l1_ratio=0.5) の精度: {accuracy_elastic:.4f}")

解説:

  • l1_ratio: penalty='elasticnet'の場合に、L1とL2の混合比率を制御します(0でL2のみ、1でL1のみ)。
  • alpha: 正則化の強さを制御します。値が大きいほど正則化が強くなります。
  • penalty: 'l1', 'l2', 'elasticnet' のいずれかを指定します。
    • 'l2': L2正則化(リッジ回帰に相当)。重みを小さく保ち、過学習を抑制します。
    • 'l1': L1正則化(ラッソ回帰に相当)。一部の重みをゼロにする効果があり、特徴量選択に役立ちます。
    • 'elasticnet': L1とL2の混合です。

ハイパーパラメータチューニング (GridSearchCV)

最適なハイパーパラメータの組み合わせを見つけるためにGridSearchCVを使用します。

print("\n--- SGDClassifier: ハイパーパラメータチューニング ---")
# 探索するハイパーパラメータの範囲を定義
param_grid = {
    'sgd_clf__loss': ['hinge', 'log_loss'],
    'sgd_clf__penalty': ['l2', 'l1', 'elasticnet'],
    'sgd_clf__alpha': [0.0001, 0.001, 0.01],
    'sgd_clf__l1_ratio': [0.15, 0.5, 0.85], # elasticnetの場合のみ適用
    'sgd_clf__max_iter': [1000], # 通常は十分な数を設定し、tolで早期停止に任せる
    'sgd_clf__tol': [1e-3]
}

# パイプラインを定義 (グリッドサーチの対象とするモデルはsgd_clf)
pipeline_tune = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_clf', SGDClassifier(random_state=42))
])

# GridSearchCVの設定
# cv=5: 5分割交差検定
# n_jobs=-1: 利用可能な全てのCPUコアを使用
grid_search_clf = GridSearchCV(pipeline_tune, param_grid, cv=5, n_jobs=-1, verbose=1)
grid_search_clf.fit(X_train, y_train)

print(f"ベストスコア: {grid_search_clf.best_score_:.4f}")
print(f"ベストハイパーパラメータ: {grid_search_clf.best_params_}")

# ベストモデルでの予測と評価
y_pred_best_clf = grid_search_clf.predict(X_test)
accuracy_best_clf = accuracy_score(y_test, y_pred_best_clf)
print(f"チューニング後のテスト精度: {accuracy_best_clf:.4f}")

解説:

  • verbose=1: 実行状況を表示します。
  • n_jobs=-1: 計算を並列化し、処理時間を短縮します。
  • GridSearchCV: 指定された全てのパラメータの組み合わせを試行し、交差検定によって最適な組み合わせを見つけます。
  • param_grid: 辞書形式で、探索したいハイパーパラメータ名とその候補値を指定します。パイプライン内のモデルのパラメータを指定する場合、'ステップ名__パラメータ名' の形式で記述します(例: sgd_clf__loss)。

オンライン学習 / partial_fit の使用

SGDClassifierpartial_fitメソッドをサポートしており、メモリに収まらない大規模データや、データがストリーム形式で逐次的に入ってくる場合に有用です。

print("\n--- SGDClassifier: オンライン学習 (partial_fit) ---")
# 非常に大きなデータセットをシミュレート
X_large, y_large = make_classification(n_samples=100000, n_features=20, n_classes=2, random_state=42)

# StandardScalerもpartial_fitをサポートしないため、別々に扱うか、パイプラインの最後のステップで呼び出す
# ここではシンプルに、まず全データでスケーラーをfit_transformし、その後の学習をpartial_fitで行う例
# 理想的には、スケーラーもバッチ学習に対応するべきですが、ここでは簡単化のため。
scaler_online = StandardScaler()
X_large_scaled = scaler_online.fit_transform(X_large)

# SGDClassifierを初期化
sgd_online = SGDClassifier(loss='log_loss', max_iter=1, tol=None, random_state=42, warm_start=True)
# warm_start=True: fit()が呼び出されるたびに学習が継続される

# バッチサイズを定義
batch_size = 1000
n_batches = int(np.ceil(len(X_large_scaled) / batch_size))

# 全てのユニークなクラスラベルを最初に渡すことが重要
# SGDClassifierは、最初にpartial_fitが呼ばれたときに、どのクラスが存在するかを知る必要がある
unique_classes = np.unique(y_large)
print(f"学習するクラス: {unique_classes}")

for i in range(n_batches):
    start = i * batch_size
    end = start + batch_size
    X_batch = X_large_scaled[start:end]
    y_batch = y_large[start:end]

    # partial_fitで学習を進める
    # 最初のバッチでclasses引数を渡す
    if i == 0:
        sgd_online.partial_fit(X_batch, y_batch, classes=unique_classes)
    else:
        sgd_online.partial_fit(X_batch, y_batch)
    
    if i % 10 == 0:
        print(f"Processed batch {i+1}/{n_batches}")

# テストデータで評価
X_test_scaled = scaler_online.transform(X_test) # スケーラーは訓練データ全体でfitしたものを使用
y_pred_online = sgd_online.predict(X_test_scaled)
accuracy_online = accuracy_score(y_test, y_pred_online)
print(f"オンライン学習後のテスト精度: {accuracy_online:.4f}")

解説:

  • classes=unique_classes: partial_fitの最初の呼び出し時に、訓練データに存在する可能性のある全てのクラスラベルを渡す必要があります。これは、モデルが内部的にクラスのリストを初期化するためです。
  • warm_start=True: fitメソッドが呼ばれても、モデルのパラメータをリセットせずに学習を継続させます。partial_fitで複数回学習を継続する場合に必要です。
  • partial_fit: データの一部(バッチ)を使ってモデルをインクリメンタルに学習させます。

回帰タスクにSGDRegressorを使用します。

# 1. ダミーデータの生成 (回帰)
X_reg, y_reg = make_regression(n_samples=1000, n_features=20, n_informative=10, random_state=42)
X_train_reg, X_test_reg, y_train_reg, y_test_reg = train_test_split(X_reg, y_reg, test_size=0.3, random_state=42)

# 2. モデルの構築と学習
print("\n--- SGDRegressor: 基本的な使い方 ---")
pipeline_reg = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_reg', SGDRegressor(max_iter=1000, tol=1e-3, random_state=42))
])

pipeline_reg.fit(X_train_reg, y_train_reg)

# 3. 予測と評価
y_pred_reg = pipeline_reg.predict(X_test_reg)
rmse = np.sqrt(mean_squared_error(y_test_reg, y_pred_reg))
r2 = r2_score(y_test_reg, y_pred_reg)
print(f"RMSE: {rmse:.4f}")
print(f"R^2 スコア: {r2:.4f}")

解説:

  • SGDRegressor: 確率的勾配降下法による回帰器。
    • lossのデフォルトは'squared_error' (通常の最小二乗法) です。
    • 評価指標はRMSE (二乗平均平方根誤差) とR^2スコアを使用します。
  • make_regression: 1000個のサンプルと20個の特徴量を持つ回帰用のダミーデータを生成します。

SGDRegressorも、回帰用の損失関数を選択できます。

print("\n--- SGDRegressor: 異なる損失関数の選択 ---")
# Huber損失 (外れ値に強い)
pipeline_huber = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_huber', SGDRegressor(loss='huber', max_iter=1000, tol=1e-3, random_state=42))
])
pipeline_huber.fit(X_train_reg, y_train_reg)
rmse_huber = np.sqrt(mean_squared_error(y_test_reg, pipeline_huber.predict(X_test_reg)))
print(f"Huber損失のRMSE: {rmse_huber:.4f}")

# Epsilon-Insensitive損失 (SVRに似た挙動)
pipeline_epsilon = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_epsilon', SGDRegressor(loss='epsilon_insensitive', epsilon=0.1, max_iter=1000, tol=1e-3, random_state=42))
])
pipeline_epsilon.fit(X_train_reg, y_train_reg)
rmse_epsilon = np.sqrt(mean_squared_error(y_test_reg, pipeline_epsilon.predict(X_test_reg)))
print(f"Epsilon-Insensitive損失のRMSE: {rmse_epsilon:.4f}")

解説:

  • loss='epsilon_insensitive': Epsilon-Insensitive損失。サポートベクター回帰 (SVR) に似た損失関数で、予測値と実際の値の差がepsilonの範囲内であれば損失を0とします。
  • loss='huber': Huber損失。二乗誤差と絶対誤差を組み合わせた損失関数で、外れ値の影響を受けにくい特性があります。

ハイパーパラメータチューニング (SGDRegressor)

SGDClassifierと同様に、SGDRegressorGridSearchCVを使ってチューニングできます。

print("\n--- SGDRegressor: ハイパーパラメータチューニング ---")
param_grid_reg = {
    'sgd_reg__loss': ['squared_error', 'huber', 'epsilon_insensitive'],
    'sgd_reg__alpha': [0.0001, 0.001],
    'sgd_reg__penalty': ['l2', 'l1'],
    'sgd_reg__max_iter': [1000],
    'sgd_reg__tol': [1e-3]
}

pipeline_tune_reg = Pipeline([
    ('scaler', StandardScaler()),
    ('sgd_reg', SGDRegressor(random_state=42))
])

grid_search_reg = GridSearchCV(pipeline_tune_reg, param_grid_reg, cv=5, n_jobs=-1, verbose=1, scoring='neg_mean_squared_error')
# 回帰問題では、'neg_mean_squared_error' (負のMSE) を使用する
grid_search_reg.fit(X_train_reg, y_train_reg)

print(f"ベストスコア (負のMSE): {grid_search_reg.best_score_:.4f}")
print(f"ベストハイパーパラメータ: {grid_search_reg.best_params_}")

y_pred_best_reg = grid_search_reg.predict(X_test_reg)
rmse_best_reg = np.sqrt(mean_squared_error(y_test_reg, y_pred_best_reg))
print(f"チューニング後のRMSE: {rmse_best_reg:.4f}")

解説:

  • 回帰問題の場合、GridSearchCVscoringパラメータには'neg_mean_squared_error' (負の平均二乗誤差) を指定することが一般的です。これは、GridSearchCVがスコアを最大化しようとするため、誤差を最小化したい場合は負の値を指定する必要があります。


SGDが特に大規模データセットやオンライン学習で強みを発揮する一方で、以下に示す代替手法は、より安定した収束、異なる種類の正則化、あるいは異なる最適化アプローチを提供します。

ロジスティック回帰 (Logistic Regression)

目的: 分類問題 scikit-learnクラス: sklearn.linear_model.LogisticRegression

ロジスティック回帰は、分類問題で広く使われる線形モデルです。SGDClassifierloss='log_loss'を指定した場合と理論的には同じモデルを学習しますが、内部の最適化アルゴリズムが異なります。LogisticRegressionは、デフォルトでLBFGS、SAG、SAGAなどのより高度な最適化ソルバーを使用します。これらのソルバーは、SGDよりも安定して、かつ高速に収束することが多いです。

特徴:

  • 多クラス分類: One-vs-Rest (OvR) または Multinomial (多項ロジスティック回帰) の戦略をサポートします。
  • 小~中規模データ: データセットがメモリに収まる範囲であれば、SGDよりも一般的に優れたパフォーマンスを発揮します。
  • 正則化: L1、L2正則化がサポートされており、penaltyパラメータで指定します。正則化の強さはCパラメータ(alphaの逆数のようなもの)で制御します。
  • 安定した収束: デフォルトのソルバーは、SGDに比べてより安定した収束を示します。

SGDとの使い分け:

  • データセットが中規模以下で、より安定した収束や、高度な最適化アルゴリズムの恩恵を受けたい場合はLogisticRegression
  • データセットが大規模で、メモリに収まらない場合やオンライン学習が必要な場合はSGDClassifier

コード例:

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# ダミーデータの生成
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

print("--- LogisticRegressionの例 ---")
# パイプライン: StandardScalerでデータを標準化し、LogisticRegressionを適用
# solver='liblinear' はL1/L2正則化に対応しており、比較的小規模なデータセットに適しています。
# C: 正則化の強さ (小さいほど正則化が強い)
pipeline_lr = Pipeline([
    ('scaler', StandardScaler()),
    ('lr', LogisticRegression(random_state=42, solver='liblinear', C=1.0))
])

pipeline_lr.fit(X_train, y_train)
y_pred_lr = pipeline_lr.predict(X_test)
accuracy_lr = accuracy_score(y_test, y_pred_lr)
print(f"LogisticRegressionのテスト精度: {accuracy_lr:.4f}")

サポートベクターマシン (Support Vector Machine)

目的: 分類 (SVC) または回帰 (SVR) scikit-learnクラス: sklearn.svm.SVC, sklearn.svm.SVR, sklearn.svm.LinearSVC, sklearn.svm.LinearSVR

SVMは、分類と回帰の両方に使われる強力な線形・非線形モデルです。SGDClassifierloss='hinge'を指定すると線形SVMに相当しますが、SVCLinearSVCは異なる最適化アルゴリズムを使用します。特にLinearSVCは、線形カーネルのSVMに特化しており、大規模データセットでも効率的に動作しますが、SGDClassifierとは異なるソルバーを使用します。

特徴:

  • LinearSVC/LinearSVR: 線形SVMに特化しており、特に大規模なデータセットに対してSGDに次ぐ効率性を持つことがあります。partial_fitはサポートしていません。
  • マージン最大化: 決定境界と最も近いサンプル(サポートベクター)との距離(マージン)を最大化することで、汎化性能を高めます。
  • 線形および非線形分離: カーネルトリックを使用することで非線形分類も可能(SVCSVR)。

SGDとの使い分け:

  • 非線形分類が必要な場合はSVC(ただし、大規模データには不向き)。
  • 線形分類で、より厳密なマージン最大化の恩恵を受けたい場合や、partial_fitが不要な場合はLinearSVC
  • 非常に大規模な線形問題でオンライン学習が必要な場合はSGDClassifier

コード例:

from sklearn.svm import LinearSVC
# その他のインポートは前述と同じ

print("\n--- LinearSVCの例 ---")
# パイプライン: StandardScalerでデータを標準化し、LinearSVCを適用
# dual=False: サンプル数が多い場合に推奨される最適化アルゴリズム
# C: 正則化の強さ (小さいほど正則化が強い)
pipeline_svc = Pipeline([
    ('scaler', StandardScaler()),
    ('svc', LinearSVC(random_state=42, dual=False, C=1.0, max_iter=2000))
])

pipeline_svc.fit(X_train, y_train)
y_pred_svc = pipeline_svc.predict(X_test)
accuracy_svc = accuracy_score(y_test, y_pred_svc)
print(f"LinearSVCのテスト精度: {accuracy_svc:.4f}")

線形回帰 (Linear Regression)

目的: 回帰問題 scikit-learnクラス: sklearn.linear_model.LinearRegression

LinearRegressionは、最も基本的な線形回帰モデルであり、通常の最小二乗法(OLS)を用いてパラメータを直接計算します。SGDRegressorloss='squared_error'を指定した場合と目的関数は同じですが、LinearRegressionは解析的に(または行列演算で)解を求めるため、反復的な最適化は行いません。

特徴:

  • 正則化なし: 標準のLinearRegressionは正則化を行いません。正則化が必要な場合は、RidgeLassoを使用します。
  • 高速: 一度計算すれば最適解が得られるため、SGDのような収束の懸念がありません。
  • 解析的解法: データがメモリに収まる限り、最適な解を直接計算します。

SGDとの使い分け:

  • データセットが中規模以下で、解析的解法による精度と安定性を求める場合はLinearRegression
  • データセットが大規模で、メモリに収まらない場合やオンライン学習が必要な場合はSGDRegressor

コード例:

from sklearn.linear_model import LinearRegression
# その他のインポートは前述と同じ

# 回帰用ダミーデータの生成
X_reg, y_reg = make_regression(n_samples=1000, n_features=20, n_informative=10, random_state=42)
X_train_reg, X_test_reg, y_train_reg, y_test_reg = train_test_split(X_reg, y_reg, test_size=0.3, random_state=42)

print("\n--- LinearRegressionの例 ---")
# StandardScalerは必須ではありませんが、特徴量のスケールを揃えることで他のモデルとの比較がしやすくなります。
pipeline_lin_reg = Pipeline([
    ('scaler', StandardScaler()),
    ('lin_reg', LinearRegression())
])

pipeline_lin_reg.fit(X_train_reg, y_train_reg)
y_pred_lin_reg = pipeline_lin_reg.predict(X_test_reg)
rmse_lin_reg = np.sqrt(mean_squared_error(y_test_reg, y_pred_lin_reg))
r2_lin_reg = r2_score(y_test_reg, y_pred_lin_reg)
print(f"LinearRegressionのRMSE: {rmse_lin_reg:.4f}")
print(f"LinearRegressionのR^2 スコア: {r2_lin_reg:.4f}")

リッジ回帰 (Ridge) およびラッソ回帰 (Lasso)

目的: 回帰問題 (正則化された線形回帰) scikit-learnクラス: sklearn.linear_model.Ridge, sklearn.linear_model.Lasso

これらのモデルは、LinearRegressionにそれぞれL2正則化(Ridge)とL1正則化(Lasso)を適用したものです。SGDRegressorでも正則化は可能ですが、これらのクラスは正則化された線形モデルに特化しており、異なる最適化アルゴリズムを使用します。

特徴:

  • Lasso: L1正則化を適用し、一部の係数をゼロにすることで特徴量選択の効果も持ちます。
  • Ridge: L2正則化を適用し、多重共線性の問題を軽減し、モデルの汎化性能を高めます。

SGDとの使い分け:

  • 非常に大規模なデータセットで、正則化も行いたい場合はSGDRegressorpenaltyパラメータで正則化タイプを指定)。
  • 正則化された線形回帰モデルが必要で、データがメモリに収まる場合はRidge/Lasso

コード例:

from sklearn.linear_model import Ridge, Lasso
# その他のインポートは前述と同じ

print("\n--- RidgeとLassoの例 ---")
# Ridge回帰
pipeline_ridge = Pipeline([
    ('scaler', StandardScaler()),
    ('ridge', Ridge(alpha=1.0, random_state=42)) # alphaは正則化の強さ (大きいほど正則化が強い)
])
pipeline_ridge.fit(X_train_reg, y_train_reg)
rmse_ridge = np.sqrt(mean_squared_error(y_test_reg, pipeline_ridge.predict(X_test_reg)))
print(f"Ridge回帰のRMSE: {rmse_ridge:.4f}")

# Lasso回帰
pipeline_lasso = Pipeline([
    ('scaler', StandardScaler()),
    ('lasso', Lasso(alpha=0.1, random_state=42)) # alphaは正則化の強さ
])
pipeline_lasso.fit(X_train_reg, y_train_reg)
rmse_lasso = np.sqrt(mean_squared_error(y_test_reg, pipeline_lasso.predict(X_test_reg)))
print(f"Lasso回帰のRMSE: {rmse_lasso:.4f}")

scikit-learnで線形モデルを扱う際、SGDは特に大規模なデータセットやオンライン学習において非常に有用です。しかし、データセットの規模、計算資源、そして求めるモデルの特性(例えば、より安定した収束や、特定の正則化の挙動)に応じて、上記で紹介したLogisticRegressionLinearSVC/LinearSVRLinearRegressionRidge/Lassoなどの代替手法を検討することが重要です。