MLエンジニアへの道 #7 - 正則化

Last Edited: 8/6/2024

このブログ記事では、機械学習における重要な概念である正則化を紹介します。

ML

リッジ回帰

極端なケースで訓練データが2つしかない場合、訓練データと全く誤差がない線形関数をフィットさせ、バイアスを0にすることができます。

しかし、上記の例が示す通り、バイアスが低すぎると、バイアスバリアンストレードオフのせいでバリアンスが高くなってしまいます。(バイアスバリアンストレードオフ についてよくわからない場合は、交差検証に関する前回の記事をチェックしてください。)では、このようなデータが少なすぎて起こる過学習を防ぐには どうすれば良いのでしょうか?一つの解決方法として、リッジ回帰またはL2正規化があります。リッジ回帰では、コスト関数にリッジ回帰ペナルティを追加します。

J(ϕ)=MSE+λϕs2 J(\phi) = MSE + \lambda \sum \phi_s^2

ここでϕs\phi_sは線形関数の傾きです。傾きの二乗を追加することで、(負の傾きであっても)傾きが急な場合にペナルティを課し、予測が訓練データに 対して敏感になりすぎないようにします。λ\lambdaは正則化率であり、ペナルティをどれだけ追加するかを制御できます。これは、交差検証で調整できる ハイパーパラメータの一つです。

ラッソ回帰

二乗を追加する代わりに、絶対値を使用して、傾きが急な場合にペナルティを課すことができます。

J(ϕ)=MSE+λϕs J(\phi) = MSE + \lambda \sum |\phi_s|

これはラッソ回帰またはL1正則化と呼ばれます。

L1とL2正則化の違い

正則化のどちらを使うかは重要ではないと思うかもしれませんが、ここで両方を紹介した理由があります。モデルを訓練するとき、勾配を計算し、 その勾配をパラメータから引きます。したがって、リッジ回帰とラッソ回帰での勾配を計算してみましょう。リッジ回帰(L2)の場合、勾配は以下の ようになります:

ϕJ=MSEϕ+2λϕ \frac{\partial}{\partial \phi} J = \frac{\partial MSE}{\partial \phi} + 2 \lambda \phi

ラッソ回帰(L1)の場合、以下のようになります:

ϕJ=MSEϕ+λ \frac{\partial}{\partial \phi} J = \frac{\partial MSE}{\partial \phi} + \lambda

これらは非常に似ていますが、L1とL2正則化の間でペナルティ項が異なることがわかります。L1正則化では、傾きの値にかかわらずλ\lambda を引きます。したがって、傾きをゼロにすることを促します。しかし、L2正則化では2λϕ2 \lambda \phiを引くため、傾きの値が小さくなるとペナルティ項も小さくなります。 したがって、L2正則化は傾きをゼロに近づけることを促しますが、傾きをゼロにすることはありません。

したがって、無意味な変数の傾きをゼロにすることで式から自動的に除外したい場合は、ラッソ回帰またはL1正則化を使用できます。 しかし、すべての変数が予測に役に立つことがわかっている場合は、すべての傾きをゼロ以外 の値に保ちながら過剰適合を防ぐことができる、リッジ回帰またはL2正則化を使用すると良いでしょう。

エラスティックネット回帰

どちらを使用するかを選択するのが容易な場合もありますが、変数の数が非常に多いため、無用な変数があるかどうかを判断できない場合もあります。 その場合は、以下のように、両方を組み合わせたエラスティックネット回帰を使用できます。

J(ϕ)=MSE+λ1ϕs+λ2ϕs2 J(\phi) = MSE + \lambda_1 |\phi_s| + \lambda_2 \phi_s^2

L1とL2を組み合わせることで、不要な説明変数を排除しながら、他の有用な変数の傾きを小さくすることを促すことができます。しかし、調整する ハイパーパラメータや計算量が増えることを念頭に置かなければなりません。

コードの実装

LinearRegressionGDに正則化機能を組み込みましょう。

class LinearRegressionGD():
  def __init__(self, lr=0.01, regularization="l1", alpha=0.01, beta=0.01):
    self.W = np.zeros(X.shape[1])
    self.b = 0
    self.lr = lr  # 学習率
    self.history = []  # 損失の履歴
    self.regularization = regularization
    self.alpha = alpha  # lambda (エラスティックネットのlambda_1)
    self.beta = beta  # エラスティックネットのlambda_2
 
  def predict(self, X):
    return np.sum(self.W*X + self.b, axis=1)
 
  def gradient(self, X, y, pred):
      diff = pred - y
      grad_W = np.sum((1/n)*diff[:, np.newaxis]*X, axis=0)
      grad_b = np.sum((1/n)*diff)
 
      # 正則化
      if (self.regularzation == "l1"):
        grad_W += self.alpha
      elif (self.regularization == "l2"):
        grad_W += 2 * self.alpha * self.W
      elif (self.regularization == "elastic-net"):
        gradW += self.alpha + 2 * self.beta * self.W
 
      return grad_W, grad_b
        
  def fit(self, X, y, epochs=100):
    for i in range(epochs):
      pred = self.predict(X)
      n = len(y)
 
      self.history.append(mean_squared_error(y, pred))
 
      grad_W, grad_b = gradient(X, y, pred)
 
      self.W -= self.lr * grad_W
      self.b -= self.lr * grad_b
    return self.history

上記を使用して、以下のようにリッジ、ラッソ、およびエラスティックネット回帰を使用できます:

l1 = LinearRegressionGD(regularization="l1", alpha=0.01)
l2 = LinearRegressionGD(regularization="l2", alpha=0.01)
en = LinearRegreessionGD(regularization="elastic-net", alpha=0.01, beta=0.01)

交差検証を使って、正規化の方法とそのパラメータを最適化することができます。練習としてLogisticRegressionGDSoftmaxRegressionGDに 正規化機能を実装してみてください。また、時間があれば交差検証をしてみることもおすすめします。

[豆知識] ベイズ統計からの解釈

私は幸運にも、YouTubeでritvikmath(2021)のBayesian Linear Regression : Data Science Concepts というタイトルの動画を見つけました。この動画は、リッジ回帰とラッソ回帰をベイズ統計の観点から興味深い解釈を提示しています。簡単に言うと、この動画では、MSEがパラメータを与えられたデータを 観測する際の負の対数尤度に対応し、リッジ回帰とラッソ回帰の正則化項がそれぞれガウス事前分布とラプラス事前分布に対応することを説明しています。 素晴らしい動画なので、ぜひご覧になることをお勧めします。

リソース