SGDClassifierの代替手法を比較:あなたのデータに最適な線形分類器は?
scikit-learn
の linear_model.SGDClassifier
は、確率的勾配降下法 (Stochastic Gradient Descent; SGD) という最適化アルゴリズムを用いて線形モデルを学習する分類器です。
簡単に言うと、以下のような特徴があります。
- オンライン学習/アウトオブコア学習に対応
partial_fit
メソッドを使用することで、すべてのデータを一度にメモリに読み込む必要がない、逐次的な学習(オンライン学習)や、メモリに収まらない大規模なデータセットの学習(アウトオブコア学習)が可能です。 - 確率的勾配降下法 (SGD) を利用
従来の勾配降下法がすべての訓練データを使って勾配を計算するのに対し、SGDは一度に1つ(または少数のミニバッチ)の訓練データを使って勾配を推定し、モデルのパラメータを更新します。これにより、データセットが非常に大きい場合でも効率的に学習を進めることができます。 - 線形分類器
データを線形な境界線(または超平面)で分割して分類を行います。サポートベクトルマシン (SVM) やロジスティック回帰のような線形モデルの学習が可能です。
なぜSGDを使うのか?
大規模なデータセットでは、すべてのデータを使ってモデルを更新する従来の勾配降下法は計算コストが非常に高くなります。SGDは、一部のデータ(多くの場合1つ)で勾配を推定し、高速にパラメータを更新していくため、大規模データにおいて特に有効です。ただし、更新がランダムな性質を持つため、勾配降下法よりも収束が不安定になる場合があります。
主なパラメータ
fit_intercept
: 切片(intercept)を計算するかどうか。デフォルトはTrue
。eta0
: 初期学習率。learning_rate
が'constant'
や'invscaling'
の場合に重要。learning_rate
: 学習率のスケジュール。'optimal'
(デフォルト),'constant'
,'invscaling'
などがあります。tol
: 収束判定のための許容誤差。損失がこの値以下になったら学習を停止します。max_iter
: 学習のエポック数(訓練データを何回繰り返すか)。l1_ratio
:'elasticnet'
を選んだ場合のL1とL2の比率。0〜1の範囲。alpha
: 正則化の強さを制御する定数。デフォルトは0.0001。penalty
: 正則化項。上記参照。loss
: 損失関数。上記参照。
from sklearn.linear_model import SGDClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# データセットの準備
iris = load_iris()
X, y = iris.data, iris.target
# 訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# SGDClassifierのインスタンス化
# loss='hinge' (SVM), penalty='l2' (L2正則化) を指定
sgd_clf = SGDClassifier(loss='hinge', penalty='l2', max_iter=1000, tol=1e-3, random_state=42)
# モデルの訓練
sgd_clf.fit(X_train, y_train)
# 予測
y_pred = sgd_clf.predict(X_test)
# 精度評価
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.2f}")
# ロジスティック回帰として使う場合
sgd_logreg = SGDClassifier(loss='log', penalty='l2', max_iter=1000, tol=1e-3, random_state=42)
sgd_logreg.fit(X_train, y_train)
y_pred_logreg = sgd_logreg.predict(X_test)
accuracy_logreg = accuracy_score(y_test, y_pred_logreg)
print(f"Logistic Regression Accuracy: {accuracy_logreg:.2f}")
特徴量スケーリング (Feature Scaling)
これは SGDClassifier
を使う上で最も重要と言っても過言ではありません。SGDは勾配に基づいてパラメータを更新するため、特徴量のスケールが大きく異なると、学習が不安定になったり、収束が非常に遅くなったり、そもそも正しい解に到達しなかったりします。
問題点
- 性能の低下
モデルが最適な分離境界を見つけられない。 - 収束の遅延/失敗
損失関数の最適化がうまく進まない。 - 学習の不安定化
スケールの大きい特徴量が勾配の更新を支配し、他の特徴量の学習がほとんど進まない。
トラブルシューティング
- パイプラインの使用
データの前処理とモデル学習を組み合わせることで、スケーリングを自動的に適用し、コードを簡潔に保つことができます。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import SGDClassifier pipeline = Pipeline([ ('scaler', StandardScaler()), ('sgd_clf', SGDClassifier(random_state=42)) ]) pipeline.fit(X_train, y_train)
- Min-Maxスケーリング (Min-Max Scaling)
各特徴量を0から1の範囲にスケーリングします。sklearn.preprocessing.MinMaxScaler
を使用します。from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)
- 標準化 (Standardization)
各特徴量の平均を0、標準偏差を1にするのが一般的です。sklearn.preprocessing.StandardScaler
を使用します。from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # テストデータにも同じスケーリングを適用
学習率 (Learning Rate) と収束 (Convergence)
SGDは、各イテレーションで少しずつモデルを更新するため、学習率(eta0
と learning_rate
パラメータ)の設定が非常に重要です。
問題点
- tol (許容誤差) の設定
tol
が厳しすぎると、モデルが収束に達する前に学習が停止しない可能性があります。 - max_iter が不足している
学習が収束する前にイテレーションが終了してしまう。 - 学習率が低すぎる
収束が非常に遅く、学習に時間がかかりすぎる。 - 学習率が高すぎる
損失が発散したり、最適な解を飛び越えてしまったりして、収束しない。
トラブルシューティング
- learning_rate スケジュール
- デフォルトの
'optimal'
は、学習率を自動的に調整してくれるため、多くのケースで良い出発点となります。 'constant'
を使う場合は、eta0
をグリッドサーチなどで最適化する必要があります。- 学習曲線(訓練損失と検証損失の推移)をプロットし、学習が適切に進んでいるかを確認します。
- デフォルトの
- tol の調整
tol
(stopping criterion) を調整します。デフォルトは 10−3 ですが、より高い精度が必要な場合は小さく、収束を早めたい場合は大きくします。ただし、max_iter
に到達する前にtol
によって停止するため、両者のバランスが重要です。 - max_iter の調整
十分なエポック数を確保します。データセットのサイズにもよりますが、通常は数百から数千の範囲で試します。scikit-learnのドキュメントでは、経験的に10^6のサンプルを観測するとSGDは収束するとされており、n_iter = np.ceil(10**6 / n_samples)
を目安にするよう提案されています(n_iter
は古いパラメータ名で、現在はmax_iter
に相当します)。
過学習 (Overfitting) と未学習 (Underfitting)
他の機械学習モデルと同様に、SGDClassifier
も過学習や未学習の問題に直面することがあります。
問題点
- 未学習
モデルがデータの特徴を捉えきれず、訓練データでもテストデータでも性能が低い。 - 過学習
訓練データには非常にうまくフィットするが、未知のデータに対する汎化性能が低い。
トラブルシューティング
- データ量の調整
- 未学習の場合
より多くの訓練データがあれば、モデルの性能が向上する可能性があります。 - 過学習の場合
訓練データを増やすことで、モデルがより多くのパターンを学習し、汎化性能が向上する可能性があります。
- 未学習の場合
- 特徴量の増減
- 未学習の場合
より多くの特徴量を追加する、特徴量エンジニアリングを行う、またはより複雑なモデルを検討する。 - 過学習の場合
不要な特徴量を削除する、次元削減 (PCAなど) を適用する。
- 未学習の場合
- 正則化 (Regularization)
penalty
(正則化項) とalpha
(正則化の強さ) パラメータを調整します。penalty='l2'
(Ridge相当): L2正則化は大きな係数にペナルティを与え、モデルの複雑さを抑えます。penalty='l1'
(Lasso相当): L1正則化は一部の係数を0にする傾向があり、特徴量選択の効果も期待できます。penalty='elasticnet'
(L1とL2の組み合わせ):l1_ratio
でL1とL2のバランスを調整します。alpha
の値を調整します。alpha
が大きいほど正則化が強く、モデルが単純になります(未学習の可能性が高まります)。alpha
が小さいほど正則化が弱く、モデルが複雑になります(過学習の可能性が高まります)。GridSearchCV
やRandomizedSearchCV
を使って最適なalpha
を探索するのが一般的です。
クラスの不均衡 (Class Imbalance)
分類問題でクラス間のデータ数が著しく異なる場合、モデルは多数派クラスに偏って予測する傾向があります。
問題点
- 少数派クラスの予測精度が非常に低い。
トラブルシューティング
- オーバーサンプリング/アンダーサンプリング
imbalanced-learn などのライブラリを使用して、訓練データのクラス数を調整します(例: SMOTE)。 - class_weight='balanced' の使用
SGDClassifier
のclass_weight
パラメータを'balanced'
に設定すると、各クラスのデータ数に応じて自動的に重みを調整し、不均衡を緩和してくれます。sgd_clf = SGDClassifier(loss='log', class_weight='balanced', random_state=42)
多クラス分類の挙動
SGDClassifier
は、内部的には「One-vs-Rest (OvR)」戦略で多クラス分類を行います。これは、各クラスに対して二項分類器を学習させることを意味します。
問題点
predict_proba
メソッドが使えないことがある:loss='hinge'
(線形SVM) の場合、predict_proba
はサポートされていません。これは、SVMが確率ではなく決定境界を学習するためです。
トラブルシューティング
- 確率予測が必要な場合
loss='log'
(ロジスティック回帰) またはloss='modified_huber'
を使用します。これらは確率的な出力を生成します。CalibratedClassifierCV
を使って、線形SVMの出力(決定関数)を確率に変換することも可能です。
from sklearn.calibration import CalibratedClassifierCV from sklearn.linear_model import SGDClassifier sgd_clf = SGDClassifier(loss='hinge', random_state=42) calibrated_sgd_clf = CalibratedClassifierCV(sgd_clf, method='isotonic', cv=5) calibrated_sgd_clf.fit(X_train_scaled, y_train) y_proba = calibrated_sgd_clf.predict_proba(X_test_scaled)
最適なパフォーマンスを得るためには、ハイパーパラメータの探索が不可欠です。
問題点
- 手動でのパラメータ調整は非効率的で、最適な組み合わせを見つけにくい。
- ランダムサーチ (Random Search)
RandomizedSearchCV
を使用して、指定したパラメータ空間からランダムにサンプリングして探索します。特にパラメータの組み合わせが多い場合に効率的です。 - グリッドサーチ (Grid Search)
GridSearchCV
を使用して、指定したパラメータの組み合わせを網羅的に探索します。
例1: 基本的な使い方 - 線形SVMとして利用
SGDClassifier
のデフォルトの loss='hinge'
は、線形サポートベクトルマシン (SVM) と同じ目的関数を使用します。
import numpy as np
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification # サンプルデータ生成用
# 1. サンプルデータの生成
# make_classificationで2クラスの分類問題を生成します。
# n_samples: サンプル数, n_features: 特徴量数, random_state: 再現性のためのシード
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10, n_redundant=5,
n_classes=2, random_state=42)
# 2. データを訓練セットとテストセットに分割
# test_size=0.3: 30%をテストデータに
# stratify=y: クラスの比率を維持したまま分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# 3. 特徴量のスケーリング (非常に重要!)
# SGDClassifierは勾配降下法に基づくため、特徴量のスケールが大きく異なる場合、
# 学習が不安定になったり収束が遅れたりします。StandardScalerで標準化します。
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 訓練データでfit_transform
X_test_scaled = scaler.transform(X_test) # テストデータにはtransformのみ適用 (訓練データの平均と標準偏差を使用)
# 4. SGDClassifierのインスタンス化と学習
# loss='hinge': 線形SVMの損失関数 (デフォルト)
# penalty='l2': L2正則化 (デフォルト、過学習防止)
# max_iter=1000: 最大エポック数 (データセット全体を何回繰り返して学習するか)
# tol=1e-3: 収束許容誤差。損失がこの値以下になったら学習を停止
# random_state: 再現性のためのシード
sgd_clf = SGDClassifier(loss='hinge', penalty='l2', max_iter=1000, tol=1e-3, random_state=42)
print("--- 線形SVM (SGDClassifier) の学習開始 ---")
sgd_clf.fit(X_train_scaled, y_train) # モデルの学習
print("--- 学習完了 ---")
# 5. モデルの評価
y_pred = sgd_clf.predict(X_test_scaled) # テストデータで予測
accuracy = accuracy_score(y_test, y_pred) # 精度を計算
print(f"Accuracy (線形SVM): {accuracy:.4f}")
# 学習済みのモデルの係数と切片
print(f"Coefficients (係数): {sgd_clf.coef_[0][:5]}...") # 最初の5つの係数
print(f"Intercept (切片): {sgd_clf.intercept_[0]:.4f}")
解説
coef_
とintercept_
: 学習によって得られたモデルの係数(重み)と切片(バイアス)です。線形モデルの決定境界を定義します。accuracy_score
: 予測結果と実際の値(正解ラベル)を比較して、モデルの精度を計算します。predict()
: 学習済みモデルで新しいデータに対するクラスを予測します。fit()
: モデルを訓練データで学習させます。SGDClassifier
のパラメータ:loss='hinge'
: 線形SVMに相当する損失関数です。penalty='l2'
: L2正則化(Ridgeに相当)を適用し、過学習を防ぎます。max_iter
とtol
: 収束に関するパラメータです。十分なイテレーション数を確保しつつ、一定の精度に達したら学習を停止するように設定します。
StandardScaler
:SGDClassifier
を使う上で必須級の前処理です。各特徴量の平均を0、標準偏差を1にスケーリングします。訓練データでfit_transform
し、その後のテストデータにはtransform
のみ適用することが重要です。train_test_split
: データセットを訓練用とテスト用に分割し、モデルの汎化性能を評価するために使います。stratify=y
を指定することで、元のデータセットのクラス比率を維持したまま分割できます。make_classification
: ダミーの分類データセットを簡単に作成できます。
loss='log'
を指定すると、SGDClassifier
はロジスティック回帰と同じ目的関数を使用します。これにより、クラスの確率を予測できるようになります。
import numpy as np
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, log_loss
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
# 1. サンプルデータの生成 (例1と同じ)
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10, n_redundant=5,
n_classes=2, random_state=42)
# 2. データを訓練セットとテストセットに分割 (例1と同じ)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# 3. 特徴量のスケーリング (例1と同じ)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 4. SGDClassifierのインスタンス化と学習 (ロジスティック回帰として)
# loss='log': ロジスティック回帰の損失関数
# その他のパラメータは例1と同様
sgd_logreg = SGDClassifier(loss='log', penalty='l2', max_iter=1000, tol=1e-3, random_state=42)
print("\n--- ロジスティック回帰 (SGDClassifier) の学習開始 ---")
sgd_logreg.fit(X_train_scaled, y_train)
print("--- 学習完了 ---")
# 5. モデルの評価
y_pred_logreg = sg_logreg.predict(X_test_scaled)
accuracy_logreg = accuracy_score(y_test, y_pred_logreg)
print(f"Accuracy (ロジスティック回帰): {accuracy_logreg:.4f}")
# 6. 確率予測
# predict_proba() は各クラスに属する確率を返します。
# 2クラス分類の場合、[クラス0の確率, クラス1の確率] の形式で返されます。
y_proba_logreg = sgd_logreg.predict_proba(X_test_scaled)
print(f"Predicted probabilities for first 5 samples:\n {y_proba_logreg[:5].round(4)}")
# ログロス (Log Loss) の計算 (確率予測の評価指標)
logloss = log_loss(y_test, y_proba_logreg)
print(f"Log Loss (ロジスティック回帰): {logloss:.4f}")
linear_model.LogisticRegression (ロジスティック回帰)
最も一般的な線形分類器の一つです。
SGDClassifier との違い
- 大規模データ
メモリに収まらないような非常に大規模なデータセットには直接対応できません(partial_fit
がないため)。 - スケーリング
LogisticRegression
はSGDClassifier
ほど厳密な特徴量スケーリングを必要としませんが、それでもスケーリングは収束を早め、パフォーマンスを向上させることがよくあります。 - 最適化アルゴリズム
LogisticRegression
は通常、より洗練された最適化アルゴリズム(例:LBFGS、SAG、SAGAなど)を使用します。これらは、データがメモリに収まる限り、SGDClassifier
よりも高速かつ安定して収束する傾向があります。
どのような場合に使うか
- 確率予測が特に重要な場合(
predict_proba
が標準で利用可能)。 - より安定した収束と、デフォルト設定での良好なパフォーマンスを求める場合。
- データセットが中規模以下(数万~数十万サンプル程度でメモリに収まる)の場合。
コード例
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, 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)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# LogisticRegression のインスタンス化
# C: 正則化の強さの逆数 (Cが小さいほど正則化が強い)
log_reg = LogisticRegression(penalty='l2', C=1.0, solver='liblinear', random_state=42)
log_reg.fit(X_train_scaled, y_train)
y_pred = log_reg.predict(X_test_scaled)
print(f"Logistic Regression Accuracy: {accuracy_score(y_test, y_pred):.4f}")
svm.LinearSVC (線形サポートベクトル分類器)
LinearSVC
は liblinear
ライブラリをベースにした線形サポートベクトルマシンです。
SGDClassifier との違い
- 確率予測
デフォルトではpredict_proba
を持ちません (probability=True
に設定すると利用できますが、交差検証が必要なため計算コストが高くなります)。 - 学習速度とメモリ消費
中規模のデータセットではSGDClassifier
よりも高速に学習できることがありますが、大規模データではSGDClassifier
の方が優れている可能性があります(特にpartial_fit
を利用する場合)。 - 最適化アルゴリズム
LinearSVC
はヒンジ損失を最適化するために異なるアルゴリズム(通常は座標降下法)を使用します。これはSGDClassifier
の確率的勾配降下法とは異なります。
どのような場合に使うか
- クラスの境界を明確にしたい場合。
SGDClassifier
よりも比較的少ないハイパーパラメータ調整で良い結果を得たい場合。- データセットが中規模以下で、線形SVMの堅牢な性能を求める場合。
コード例
from sklearn.svm import LinearSVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, 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)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# LinearSVC のインスタンス化
# C: 正則化の強さの逆数
# loss: ヒンジ損失 (squared_hinge も選択可能)
# dual=False: サンプル数が多い場合に推奨される設定
# max_iter: 最大イテレーション数
linear_svc = LinearSVC(C=1.0, loss='hinge', dual=False, max_iter=2000, random_state=42)
linear_svc.fit(X_train_scaled, y_train)
y_pred = linear_svc.predict(X_test_scaled)
print(f"Linear SVC Accuracy: {accuracy_score(y_test, y_pred):.4f}")
naive_bayes.GaussianNB / MultinomialNB など (ナイーブベイズ分類器)
ナイーブベイズ分類器は、特徴量の間に条件付き独立性があるという「ナイーブな」仮定に基づいています。
SGDClassifier との違い
- 性能
特徴量の独立性の仮定が満たされる場合に特に強力ですが、そうでない場合でも驚くほど良い性能を示すことがあります。 - 特徴量のスケーリング
スケーリングを必要としません(距離計算に基づかないため)。 - ハイパーパラメータ
調整すべきハイパーパラメータが少ないです。 - 学習速度
非常に高速に学習でき、大規模データセットでも効率的です。 - アルゴリズム
確率モデルに基づいています。
どのような場合に使うか
- 特徴量のスケーリングが面倒な場合。
- テキスト分類(
MultinomialNB
やComplementNB
が一般的)や、カウントベースの特徴量を持つデータ。 - ベースラインモデルとして、他の複雑なモデルと比較するための出発点にしたい場合。
- 非常に高速な学習と予測が必要な場合。
コード例
from sklearn.naive_bayes import GaussianNB # 連続値データ向け
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, 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)
# GaussianNB のインスタンス化 (スケーリング不要)
gnb = GaussianNB()
gnb.fit(X_train, y_train) # スケーリングされていないデータで学習
y_pred = gnb.predict(X_test)
print(f"Gaussian Naive Bayes Accuracy: {accuracy_score(y_test, y_pred):.4f}")
線形モデルではありませんが、非常に強力な分類器であり、線形モデルでは捉えきれない複雑なパターンを学習できます。
SGDClassifier との違い
- 解釈性
線形モデルに比べて解釈性は劣ります。 - 特徴量スケーリング
一般的に特徴量スケーリングを必要としません。 - 学習時間とメモリ消費
データセットのサイズやツリーの数にもよりますが、線形モデルよりも学習に時間がかかり、メモリを消費する傾向があります。 - 性能
多くの実世界のタスクで、線形モデルよりも高い精度を達成することがよくあります。 - モデルの複雑さ
線形モデルとは異なり、非線形な決定境界を学習できます。
どのような場合に使うか
- 計算資源が許容できる範囲で、非線形なモデルを試したい場合。
- 特徴量間の複雑な相互作用を捉えたい場合。
- 最高レベルの予測性能を求める場合。
コード例
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, 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)
# RandomForestClassifier のインスタンス化
# n_estimators: 決定木の数
# random_state: 再現性のためのシード
rf_clf = RandomForestClassifier(n_estimators=100, random_state=42)
rf_clf.fit(X_train, y_train) # スケーリングされていないデータで学習
y_pred = rf_clf.predict(X_test)
print(f"Random Forest Classifier Accuracy: {accuracy_score(y_test, y_pred):.4f}")
SGDClassifier
はその高速性と大規模データ対応能力でユニークですが、データセットのサイズ、モデルの解釈性、必要とされる予測の種類(クラスか確率か)、計算資源などの要件に応じて、上記の代替手段も強力な選択肢となり得ます。