【python】リッジ回帰とラッソ回帰の重みの謎【scikit-learn】

機械学習を勉強していると最初の方に、リッジ回帰とラッソ回帰が出てくると思います。
そこでscikit-learnで、両者を実行したことがある人はいると思います。
そこでこう思った方はいないでしょうか。

「正則化項の重み(alpha)を0にしたらリッジ回帰とラッソ回帰は同じにグラフになるはず」

そもそも正則化って?

正則化というのは過学習を抑制することを指します。 詳細はクソ有能ブログがたくさんあるのでそっちを参考にするといいと思います。
そもそもリッジ回帰やラッソ回帰が何をしているのかというと損失関数に正則化項を追加して、回帰係数が大きくなりすぎるのを防いでいます。
その正則化項の違いが、リッジ回帰とラッソ回帰の違いです。
簡単に書くと
リッジ回帰

損失関数= 二乗誤差 + 重み*回帰係数の2乗の和

ラッソ回帰

損失関数= 二乗誤差 + 重み*回帰係数の絶対値の和

です。
回帰問題は基本的に二乗誤差が小さくなるように回帰式の回帰係数を調整します。しかし、そのままやると全ての点にフィットしてしまい過学習が起こります。そこで、正則化項を加えることで、回帰係数を大きくして二乗誤差を小さくさせようとすると、正則化項が大きくなってしまい、損失関数が全体が大きくなってしまうという、いじめのようなことをします。(これを勉強していてかわいそうになりました。)
 この正則化項の重みを0以上の整数に設定することにより、どのくらい過学習を抑えられるのかを決めることができます。・・・

つまり重みを0にすれば、多項式回帰もリッジ回帰もラッソ回帰も同じ形になる!!!

よくわからんから実装してみる

何言っているかわかんないのでとりあえずpythonで書いてみましょう。

import  numpy  as  np
import  matplotlib.pyplot  as  plt
from  sklearn.preprocessing  import  PolynomialFeatures
from  sklearn.linear_model  import  LinearRegression
from  sklearn.linear_model  import  Ridge
from  sklearn.linear_model  import  Lasso
data_size=20
X = np.array([18,25,35,38,50,56,59,70])
y = np.array([3,7,10,7,9,15,14,13])

X_line=np.linspace(18,70,100)
sin_X=np.sin(2.0*np.pi*X_line)
plt.scatter(X,y)


本来は直線の回帰式だといい感じになるようなダミーデータです。
こんな感じのデータを多項式回帰を使って思いっきり過学習させてみましょう。

#多項式回帰モデルを作成
#degreeで多項式次数を指定しています。
poly = PolynomialFeatures(degree=6)
poly.fit(X.reshape(-1,1))
X_poly_3=poly.transform(X.reshape(-1,1))
lin_reg_3_model=LinearRegression().fit(X_poly_3,y)
  
X_line_poly_3=poly.fit_transform(X_line.reshape(-1,1))
plt.plot(X_line,lin_reg_3_model.predict(X_line_poly_3))

plt.scatter(X,y)


思いっきり過学習していますね。

ここにリッジ回帰のグラフを重ねてみます。

poly = PolynomialFeatures(degree=6)
poly.fit(X.reshape(-1,1))
X_poly_3=poly.transform(X.reshape(-1,1))
lin_reg_3_model=LinearRegression().fit(X_poly_3,y)
#alphaが正則化項の重み
ridge_model = Ridge(alpha = 0).fit(X_poly_3,y)

X_line_poly_3=poly.fit_transform(X_line.reshape(-1,1))
plt.plot(X_line,lin_reg_3_model.predict(X_line_poly_3))
plt.plot(X_line,ridge_model.predict(X_line_poly_3))
plt.scatter(X,y)


そうすると、多項式回帰とリッジ回帰のグラフが重なりましたね。仮説通りになりました!

これは有能すぎる仮説を立ててしまったみたいですね。。。
というわけで、同様にラッソ回帰も書いてみます。

poly = PolynomialFeatures(degree=6)
poly.fit(X.reshape(-1,1))
X_poly_3=poly.transform(X.reshape(-1,1))
lin_reg_3_model=LinearRegression().fit(X_poly_3,y)
ridge_model = Ridge(alpha = 0).fit(X_poly_3,y)
lasso_model = Lasso(alpha = 0).fit(X_poly_3,y)

X_line_poly_3=poly.fit_transform(X_line.reshape(-1,1))
plt.plot(X_line,lin_reg_3_model.predict(X_line_poly_3))
plt.plot(X_line,ridge_model.predict(X_line_poly_3))
plt.plot(X_line,lasso_model.predict(X_line_poly_3))
plt.scatter(X,y)

はっ?????

話が違います。こんなの聞いていません。
キレそうです。グラフが重なりません。
パソコンの画面を割りそうです。

ということで色々調べてみました。

原因は結局何なの???

ということで、今回リッジ回帰、ラッソ回帰を書くのに使用している scikit-learnのドキュメント参照しました。
そこに答えが書いてあったわけです。
ラッソ回帰とリッジ回帰の損失関数をみてみましょう。

ラッソ回帰の損失関数
minw12nsamplesXwy22+αw1\min_{w} { \frac{1}{2n_{\text{samples}}} ||X w - y||_2 ^ 2 + \alpha ||w||_1}
リッジ回帰の損失関数
minwXwy22+αw22\min_{w} || X w - y||_2^2 + \alpha ||w||_2^2

数式の意味なぞ理解する必要ありません。敵は一目瞭然です。よくラッソ回帰の式をみてください。
12nsamples\frac{1}{2n_{\text{samples}}}
こいつです。二乗誤差の項の前に、我が物顔でいるわけです。
こいつのせいで、正則化項の重みを0にしてもリッジ回帰と異なる値が計算されてしまうのでこのような結果になってしまうわけです。

こいつのせいで何故スパースな解(使いたかっただけ)が得られているのかはいまいちよくわかっていませんが、原因が何かわかりました。

ラッソ回帰とリッジ回帰を理解するためにこのように実装していても同じにはなりません。
そして、重みを同じにしてラッソとリッジ回帰を比較しようとしても、そもそも二乗誤差の項が全然違うので単純に比較してはいけないということです。

やはりこうやって視覚化して初めて気がつくということがあるわけですね。リッジ回帰とラッソ回帰を数式を使って説明したり、実際に実装をしたりして並列に比較しているブログはまだいいです。数式を説明して,pythonで実装して比較しているブログがよくありますが、それらは


全部クソ無能です!!!!!騙されないでください!!!

私のような勘違いをする原因になります。せめて、数式とsikit-learnの実装の仕方が違うということを明記して欲しいです。