機械学習入門

【機械学習入門⑦】過学習を防ぐ正則化!L1(Lasso回帰)とL2(Ridge回帰)を詳しく解説

 

前回の記事『【機械学習入門⑥】過学習とは?回避・対策する3つの方法も解説』では、過学習について学びました。

過学習は訓練データに過剰に適合してしまうことで、その対策方法は以下の3つでした。

  • テストデータを準備する
  • モデルを簡略化する
  • 正則化する

 

今回は、過学習の対策方法の中でも「正則化」について紹介していきます。

この記事で学べる内容は、以下のとおりです。

  • 正則化について
  • Pythonを使ったLasso回帰・Ridge回帰の実装方法

 

正則化の概念は、機械学習を勉強していく上で、過学習とセットで大事な概念になります。しっかりおさえておきましょう。

 

この記事について

この記事は、機械学習を体系的に学べる「機械学習入門講座」の1つです。

 

機械学習を勉強しようと思ったとき、以下のような悩みを持ったことはないですか?

  • 機械学習を学んでみたいけど、何から勉強したら良いのか分からない
  • 機械学習の本を買ってみたけど、よく分からなくて挫折した
  • そもそも本を買うのではなく、まずは「無料」で学習してみたい

上記のような、「機械学習を勉強し始める前の悩み」を解決するために、この機械学習講座を作成しました。

 

「プログラミングスクールは高いし、参考書は途中で飽きちゃうし…」といった方は、ぜひ第1回から学習して頂けたらと思います(*’ω’*)

一覧ページへ≫

※ブログ記事なので、無料です。でも、作成に手は抜いていません。

 

正則化とは?

正則化

正則化とは、過学習を回避するための手法です。

 

具体例で確認する正則化

 

たとえば、以前学習した多項式モデルは、以下のような精度になっていました。

Train ScoreTest Score
二次元モデル0.560.51
五次元モデル0.610.50
十次元モデル0.610.45

 

これは訓練データでは上手く学習できているけど、未知のデータ(=テストデータ)を与えると、モデルが上手く機能していないことを表しています。

この現象を回避して、未知のデータに対しても上手く機能できるようにするのが、「正則化」という手法になります。

 

正則化のイメージ

 

そもそも、過学習が発生する原因は、訓練データに過剰に適合してしまうためです。

「教科書や参考書は完ペキだけど、センター試験では上手く問題が解けなかった…」といった場合、これは教科書に過学習していたことになります。

 

少し言い換えると、過学習とは「一部のパラメータ\(w\)の絶対値」が大きくなりすぎているということになります。

実際に、『【機械学習入門⑥】過学習とは?回避・対策する3つの方法も解説』で実装した、多項式モデルのパラメータ\(w\)を見てみましょう。

\(w_1\)-90657.12
\(w_2\)-69130.40
\(w_3\)93877.48
\(w_4\)-45895.36
\(w_5\)12792.76
\(w_6\)-2252.20
\(w_7\)256.06
\(w_8\)-18.31
\(w_9\)0.75
\(w_{10}\)-0.01

こうやってみると、\(w_1\)〜\(w_5\)のパラメータが、他と比較して明らかに大きすぎます。

この大きくなりすぎているパラメーターを抑制するのが、正則化の持つ役割になります。

 

正則化の目標は、テストデータの精度(=今回なら決定係数)を向上させることです。

 

正則化の種類

 

正則化には、「L1正則化」と「L2正則化」があります。

 

各正則化手法のカンタンなイメージが、以下のとおりです。

  • L1正則化 : 不要な特徴量を減らして、過学習を回避する方法
  • L2正則化 : 特徴量を減らさずに、過学習を回避する方法

要するに『特徴量を減らすか否かが違う』ってことになります。

 

なお、各正則化手法を使った線形モデルには名前が付いています。

  • L1正則化した線形モデル : Lasso回帰(ラッソ回帰)
  • L2正則化した線形モデル : Ridge回帰(リッジ回帰)

たまに「Ridge回帰を使って予測しよう」といった文面が出てきますが、それは「L2正則化した線形モデルのことを言っているんだな」と認識しておけば大丈夫です。

 

Pythonを使ったLasso回帰とRidge回帰の実装方法【Scikit-learn】

 

それでは、Lasso回帰とRidge回帰を実装して、両者の違いを確認していきましょう。

 

今回使うソースコードは、以下のとおりです。

# ### 2.1 ライブラリのインポート
import numpy as np
import pandas as pd

from sklearn.datasets import load_boston
from sklearn.linear_model import LinearRegression, Lasso, Ridge
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures

import matplotlib.pyplot as plt
import seaborn as sns

sns.set()
sns.set_style("ticks")

# ### 2.2 データの読み込み
boston = load_boston()
df = pd.DataFrame(boston.data, columns=boston.feature_names)
df['MEDV'] = boston.target

print(df.head())

# 散布図で確認
plt.figure(figsize=(10, 6))
sns.scatterplot(x=df.RM, y=df.MEDV, alpha=0.7)
plt.show()

# ### 2.3 データの準備
x = df['RM']
X = np.array(x).reshape(-1, 1)
y = df['MEDV']

print('-'*10 + '特徴量とターゲットに分割' + '-'*10)
print('X:', X.shape)
print('y:', y.shape)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# ### 2.4 Lasso回帰・Ridge回帰の実装
def get_polynomial_features(degree:int, data):
    pf = PolynomialFeatures(degree=degree, include_bias=False)
    return pf.fit_transform(data)

X_train_10 = get_polynomial_features(10, X_train)
X_test_10 = get_polynomial_features(10, X_test)

# 多項式モデルの作成
lm_10deg = LinearRegression()
lm_10deg.fit(X_train_10,y_train)

print('Poly')
print('Train Score:{:.3f}' .format(lm_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lm_10deg.score(X_test_10, y_test)))


# Ridge回帰を使用した多項式モデルの作成
ridge_10deg = Ridge(alpha=1.0, random_state=0)
ridge_10deg.fit(X_train_10,y_train)

print('Ridge')
print('Train Score:{:.3f}' .format(ridge_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(ridge_10deg.score(X_test_10, y_test)))


# Lasso回帰を使用した多項式モデルの作成
lasso_10deg = Lasso(alpha=0.01, max_iter=2000, random_state=0)
lasso_10deg.fit(X_train_10,y_train)

print('Lasso')
print('Train Score:{:.3f}' .format(lasso_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lasso_10deg.score(X_test_10, y_test)))

# ### 2.5 学習結果の可視化
X_test_a = np.array([np.sort(X_test[:,0])]).reshape(len(X_test),1)
X_test_10 = get_polynomial_features(10, X_test_a)

y_pred_10deg = lm_10deg.predict(X_test_10)
y_pred_10deg_lasso = lasso_10deg.predict(X_test_10)
y_pred_10deg_ridge = ridge_10deg.predict(X_test_10)

plt.figure(figsize=(10, 6))
sns.scatterplot(X.reshape(len(X), ), y, alpha=0.7)
sns.lineplot(x=X_test_a.reshape(len(X_test_a), ), y=y_pred_10deg, linewidth=2.5, label='Poly 10')
sns.lineplot(x=X_test_a.reshape(len(X_test_a), ), y=y_pred_10deg_ridge, linewidth=2.5, label='Ridge')
sns.lineplot(x=X_test_a.reshape(len(X_test_a), ), y=y_pred_10deg_lasso, linewidth=2.5, label='Lasso')
plt.xlabel('RM')
plt.ylabel('MEDV')
plt.show()


print(list(lm_10deg.coef_))
print(list(ridge_10deg.coef_))

 

順番に中身のソースコードを確認していきましょう。

 

STEP① : ライブラリの読み込み

import numpy as np
import pandas as pd

from sklearn.datasets import load_boston
from sklearn.linear_model import LinearRegression, Lasso, Ridge
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures

import matplotlib.pyplot as plt
import seaborn as sns

sns.set()
sns.set_style("ticks")

今回使うライブラリは上記です。

 

  • Numpy : 配列計算用ライブラリ
  • Pandas : データ操作用ライブラリ
  • Scikit-learn(sklearn) : データの準備・機械学習モデルの作成用ライブラリ
  • Matplotlib : 作図用ライブラリ①
  • Seaborn : 作図用ライブラリ②

主にScikit-learnを使って、Lasso回帰とRidge回帰を実装していきます。

 

STEP② : データの読み込み

boston = load_boston()
df = pd.DataFrame(boston.data, columns=boston.feature_names)
df['MEDV'] = boston.target

print(df.head())

# 散布図で確認
plt.figure(figsize=(10, 6))
sns.scatterplot(x=df.RM, y=df.MEDV, alpha=0.7)
plt.show()

 

今回もボストンの住宅価格を使います。

 

再掲 : ボストン住宅価格データセットの中身

変数名データの説明
CRIM犯罪発生率
ZN住居区画の密集度
INDUS非小売業の土地割合
CHASチャールズ川の周辺か否か
NOXNOx濃度
RM住居に含まれる平均部屋数
AGE1940年より前に建てられた物件の割合
DISボストン市の5つの雇用施設からの重み付き距離
RAD大きな道路へのアクセスのしやすさ
TAX1万ドルあたりの不動産税率の総計
PTRATIO教師あたりの生徒数
B対象地域に住む黒人の比率
LSTAT低所得者の割合

 

df.head()を使うことで、これから使うデータの上から5行を、Excelのようにキレイな形で確認できます。

ボストン住宅価格 データフレーム

※Jupyter notebookの場合です。基本的にJupyter notebookの使用をおすすめします。

 

もう見慣れたかもしれませんが、、、一応散布図も可視化しておきましょう。

# 散布図を使った可視化
plt.figure(figsize=(10, 6))
sns.scatterplot(x=df.RM, y=df.MEDV, alpha=0.7)
plt.show()
ボストン住宅価格 散布図

このデータに対して、Lasso回帰とRidge回帰を実装していきます。

 

STEP③ : データの準備

x = df['RM']
X = np.array(x).reshape(-1, 1)
y = df['MEDV']

print('-'*10 + '特徴量とターゲットに分割' + '-'*10)
print('X:', X.shape)
print('y:', y.shape)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

 

Lasso回帰とRidge回帰を使ったモデルを作成するための準備をしていきます。

 

特徴量はRM、予測対象はMEDVですね。これを、train_test_split()で学習用データとテスト用データに分割します。

 

STEP④ : Lasso回帰・Ridge回帰の実装

def get_polynomial_features(degree:int, data):
    pf = PolynomialFeatures(degree=degree, include_bias=False)
    return pf.fit_transform(data)

X_train_10 = get_polynomial_features(10, X_train)
X_test_10 = get_polynomial_features(10, X_test)

# 多項式モデルの作成
lm_10deg = LinearRegression()
lm_10deg.fit(X_train_10,y_train)

print('Poly')
print('Train Score:{:.3f}' .format(lm_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lm_10deg.score(X_test_10, y_test)))


# Ridge回帰を使用した多項式モデルの作成
ridge_10deg = Ridge(alpha=1.0, random_state=0)
ridge_10deg.fit(X_train_10,y_train)

print('Ridge')
print('Train Score:{:.3f}' .format(ridge_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(ridge_10deg.score(X_test_10, y_test)))


# Lasso回帰を使用した多項式モデルの作成
lasso_10deg = Lasso(alpha=0.01, max_iter=2000, random_state=0)
lasso_10deg.fit(X_train_10,y_train)

print('Lasso')
print('Train Score:{:.3f}' .format(lasso_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lasso_10deg.score(X_test_10, y_test)))

 

Lasso回帰とRidge回帰を実装するには、多項式モデルと同様に以下の手順が必要です。

  • ①十次元の特徴量を作成する(※今回は十次元で実装するので)
  • ②多項式モデルと同じ手順でモデルを作る

 

Ridge回帰やLasso回帰を実装する場合、モデルの式は多項式モデルと変わりません。

なので、多項式モデル\(y = w_0 +  w_1 x + w_2 x^2 + \cdots w_{10} x^{10}\)の式で、\(x\)以外の①で不足している特徴量を、まずは作成していきます。

 

STEP① : 十次元の特徴量を作成する

def get_polynomial_features(degree:int, data):
    pf = PolynomialFeatures(degree=degree, include_bias=False)
    return pf.fit_transform(data)

X_train_10 = get_polynomial_features(10, X_train)
X_test_10 = get_polynomial_features(10, X_test)

上記のように、scikit-learnに入っているPolynomialFeatures()を使って、十次元の特徴量を作成します。

 

以前、多項式モデルを作成したときは、以下のような二次元の特徴量を作成しました。

多項式モデル 実装 PolynomialFeatures

 

これが、今回は十次元まで特徴量を増やした形になります。

この特徴量作成が完了したら、あとはモデルを作成するだけです。

 

STEP② : 多項式モデルと同じ手順でモデルを作る

lm_10deg = LinearRegression()
lm_10deg.fit(X_train_10,y_train)

print('Poly')
print('Train Score:{:.3f}' .format(lm_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lm_10deg.score(X_test_10, y_test)))


# Ridge回帰を使用した多項式モデルの作成
ridge_10deg = Ridge(alpha=1.0, random_state=0)
ridge_10deg.fit(X_train_10,y_train)

print('Ridge')
print('Train Score:{:.3f}' .format(ridge_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(ridge_10deg.score(X_test_10, y_test)))


# Lasso回帰を使用した多項式モデルの作成
lasso_10deg = Lasso(alpha=0.01, max_iter=2000, random_state=0)
lasso_10deg.fit(X_train_10,y_train)

print('Lasso')
print('Train Score:{:.3f}' .format(lasso_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lasso_10deg.score(X_test_10, y_test)))

 

あとは、多項式モデルと同様に、LinearRegression()を使えばOKです。

念のため、前回作成した多項式モデルも一緒に、モデル作成しておきます。

 

それでは、学習結果を見てみましょう。

lm.score()を使えばモデルの決定係数を確認できます。

print('Poly')
print('Train Score:{:.3f}' .format(lm_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lm_10deg.score(X_test_10, y_test)))

print('Ridge')
print('Train Score:{:.3f}' .format(ridge_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(ridge_10deg.score(X_test_10, y_test)))

print('Lasso')
print('Train Score:{:.3f}' .format(lasso_10deg.score(X_train_10, y_train)))
print('Test Score:{:.3f}' .format(lasso_10deg.score(X_test_10, y_test)))
  • Poly
    • Train Score:0.615
    • Test Score:0.448
  • Ridge
    • Train Score:0.609
    • Test Score:0.495
  • Lasso
    • Train Score:0.599
    • Test Score:0.489

 

上記のような結果になりました。「テストデータに対する精度は、Ridge回帰が最も良い」という結果になりましたね。

 

ちなみに、Lasso回帰だけmax_iter=2000を指定しています。

これは「更新数」のことで、なぜ必要になるのかといえば、それは解析的に解が求まらないからです。

「解析的に解が求まらない」というのは、式変形だけでは数式が解けないということです。

 

※少し込み入った話になるので、今回は「よく分からないけど、簡単には解けないんだ〜」と認識しておけばOKです。

 

STEP⑤ : 学習結果の可視化

X_test_a = np.array([np.sort(X_test[:,0])]).reshape(len(X_test),1)
X_test_10 = get_polynomial_features(10, X_test_a)

y_pred_10deg = lm_10deg.predict(X_test_10)
y_pred_10deg_lasso = lasso_10deg.predict(X_test_10)
y_pred_10deg_ridge = ridge_10deg.predict(X_test_10)

plt.figure(figsize=(10, 6))
sns.scatterplot(X.reshape(len(X), ), y, alpha=0.7)
sns.lineplot(x=X_test_a.reshape(len(X_test_a), ), y=y_pred_10deg, linewidth=2.5, label='Poly 10')
sns.lineplot(x=X_test_a.reshape(len(X_test_a), ), y=y_pred_10deg_ridge, linewidth=2.5, label='Ridge')
sns.lineplot(x=X_test_a.reshape(len(X_test_a), ), y=y_pred_10deg_lasso, linewidth=2.5, label='Lasso')
plt.xlabel('RM')
plt.ylabel('MEDV')
plt.show()


print(list(lm_10deg.coef_))
print(list(ridge_10deg.coef_))

 

元データと、各モデルの予測結果を確認してみます。

  • 多項式モデル : 青線
  • Ridge回帰 : オレンジ線
  • Lasso回帰 : 緑線
Lasso回帰 Ridge回帰 結果

元の多項式モデルだと、RMが4〜6の範囲は、かなりの悪影響を受けていました。

でも、正則化することで外れ値からの悪影響を解消できていますね。

 

正則化は、大きくなりすぎているモデルのパラメータを抑制する手法でした。なので、最後にモデルのパラメータを確認しましょう。

print(list(lm_10deg.coef_))
print(list(ridge_10deg.coef_))
パラメータ多項式モデルRidge回帰
\(w_1\)-90657.12-0.16
\(w_2\)-69130.40-0.60
\(w_3\)93877.48-1.30
\(w_4\)-45895.36-1.39
\(w_5\)12792.760.29
\(w_6\)-2252.200.24
\(w_7\)256.06-0.10
\(w_8\)-18.310.02
\(w_9\)0.75-0.001
\(w_{10}\)-0.013.21

 

多項式モデルでは、かなり大きかったモデルパラメーターが、Ridge回帰することで大きくても3.2くらいまで抑えられるようになりました。

 

ほとんどのパラメーターの絶対値は、0〜1の範囲におさまっているので、しっかりと正則化できていることが分かりますね!

 

【機械学習入門⑦】正則化まとめ

 

というわけで、今回は以上です。

正則化について、理解できたのではないでしょうか。

 

次回は、もう少し正則化を深掘りして、ElasticNetについて学習していきます。

【機械学習入門⑦】Elastic Net ElasticNetとは? Lasso回帰の欠点を補うための考案されたモデル Lassoの欠点 ...

 

ElasticNetとは、Lasso回帰とRidge回帰を混ぜ合わせた正規化手法です。

詳しくは次回解説しますね!

 

この記事が良いと思ったら、ぜひ次の『機械学習入門⑧』も学んで頂けたらと思います(*’ω’*)

 

なお、『機械学習入門シリーズ』は長いので、休みながら学習を進めてみてくださいね。

休憩するときは、記事をツイートしておくと便利ですよ!

※メンションに気が付いたら、拡散する可能性高いです(`・ω・´)!

 

おすすめの記事

【機械学習入門⑦】Elastic Net ElasticNetとは? Lasso回帰の欠点を補うための考案されたモデル Lassoの欠点 ...