【Pythonコード付】CSVを入力するだけでLasso or Ridge回帰を実行

Python

こんにちは!ぼりたそです!

今回はPythonでCSVファイルを入力するだけでLassoまたはRidge回帰を実行するプログラムを作りましたので、ご紹介します。

この記事は以下のポイントでまとめています。

Point
  • Lasso & Ridge回帰とは?
     
  • 実装コードの仕組み
     
  • 実装コードと解説

それでは順に解説していきます。

スポンサーリンク

Lasso & Ridge回帰とは?

まず、Lasso、Ridge回帰とは?というところから説明していきます。

Lasso & Ridge回帰は、最小二乗法に正則化項を組み込んだ損失関数を使用して係数決定する手法です。詳細について以下にまとめました。

■Lasso回帰(最小二乗法+L1正則化項)

$$\sum_{i=1}^{N} \left( y_i – (a_1 x_{i1} + a_2 x_{i2} + … + a_n x_{in}) \right)^2 + \alpha \sum_{i=1}^{N} |a_i|$$

Lasso回帰は最小二乗法にL1正則化項を組み込んで係数を決定する手法であり、L1正則化項によって係数の絶対値の和にペナルティを課すため、不要な特徴量の係数をゼロにする傾向があります。そのため、特徴量の選択が重要な場合や、モデルの解釈性を高めたい場合に適しています。

■Ridge回帰(最小二乗法+L2正則化項)

$$\sum_{i=1}^{N} \left( y_i – (a_1 x_{i1} + a_2 x_{i2} + … + a_n x_{in}) \right)^2 + \alpha \sum_{i=1}^{N} a_i^2$$

Ridge回帰は最小二乗法にL2正則化項を組み込んで係数を決定する手法であり、L2正則化項によって、係数の2乗和にペナルティを課すため、すべての特徴量の係数を小さくする傾向があります。例え、不要な特徴量があっても係数を完全にはゼロにしないということですね。そのため、特徴選択よりもモデルの汎化性能を向上させたい場合に適しています。ただし、Lasso回帰の方が過学習を防ぐ性能は高いと言えます。

Lasso & Ridge回帰は上記の損失関数を最小化するように回帰モデルの係数を決定してくれます。なので、係数の和である正則化項を組み込むことで係数を小さく抑え、過学習を防ぐ役割を果たします。

つまり、正則化項は係数の大きさに対してペナルティを課しているということになりますね。また、正則化項のハイパーパラメータである $\alpha$ はペナルティの大きさを制御しており、 $\alpha$ が大きいほどペナルティが大きくなる(係数が小さくなる)ということになります。

通常はこの $\alpha$ はクロスバリデーションなどで最適化するのが一般的と言えます。

詳細については以下の記事にまとめていますので、参照いただければと思います。

実装コードの仕組み

次に今回実装したコードについて内容を解説していきます。

実装したコードは学習用のCSVファイルを入力することで、解析結果を出力するコードになっています。入力データと出力データについては以下の通りです。

■入力データ

  • 解析データが格納されたCSVファイル:
    目的変数が1列目、説明変数が2列目以降になっているCSVファイルを用意してください。インデックスやカラム名意外でデータに文字列などは含まないようにしてください。

■出力データ

  • 標準編回帰係数が格納されたCSVファイル:
    変数の重要度を見積もることができる標準編回帰係数がCSVとして出力されるようになっています。
     
  • VIFが格納されたCSVファイル:
    説明変数同士の多重共線性を評価する指標として使われるVIFをそれぞれの変数ごとにCSVとして出力されるようになっています。
     
  • 変数間の相関係数が格納されたCSVファイル:
    説明変数同士の相関係数について格納されたCSVファイルが出力されるようになっています。主にVIFが高い変数について、他のどの変数と相関が高いのかを確認するためのファイルになります。
     
  • 自由度調整済み決定係数が格納されたファイル:
    入力データの回帰モデルについて自由度調整済み決定係数が格納されたファイル。基本的にFold数10のクロスバリデーションで計算され、10回分の決定係数とその平均が出力されます。

実装コードと解説

それでは、実装コードとその解説をしていきます。

今回実装したコードは以下の通りです。

import pandas as pd
import numpy as np
from sklearn.model_selection import cross_val_score, LeaveOneOut, KFold
from sklearn.linear_model import Lasso, Ridge
from sklearn.preprocessing import StandardScaler
from statsmodels.tools import add_constant
from statsmodels.stats.outliers_influence import variance_inflation_factor
from skopt import BayesSearchCV
from skopt.space import Real
from skopt.learning import GaussianProcessRegressor
from skopt.learning.gaussian_process.kernels import RBF

# CSVファイルの読み込み
data = pd.read_csv('california_housing_prices.csv')

# 説明変数と目的変数に分ける
X = data.iloc[:, 1:]
y = data.iloc[:, :1]

# データの標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
y_scaled = scaler.fit_transform(y.values.reshape(-1, 1)).flatten()

# 定数項を追加
X_scaled_with_const = add_constant(X_scaled)

# モデルの選択 ('lasso' または 'ridge')
model_type = 'lasso'  # ここを 'ridge' に変更することでRidge回帰を実行します

# ベイズ最適化のための探索空間とガウス過程回帰の設定
search_spaces = {'alpha': Real(1e-4, 10e0, prior='log-uniform')}
kernel = RBF()
gp = GaussianProcessRegressor(kernel=kernel)

if model_type == 'lasso':
    # Lasso回帰のalphaをベイズ最適化する
    opt = BayesSearchCV(
        Lasso(), search_spaces, n_iter=50, cv=5, scoring='r2', n_jobs=-1, random_state=1,
        optimizer_kwargs={'base_estimator': gp, 'acq_func': 'EI'}
    )
elif model_type == 'ridge':
    # Ridge回帰のalphaをベイズ最適化する
    opt = BayesSearchCV(
        Ridge(), search_spaces, n_iter=50, cv=5, scoring='r2', n_jobs=-1, random_state=1,
        optimizer_kwargs={'base_estimator': gp, 'acq_func': 'EI'}
    )
else:
    raise ValueError("model_type should be either 'lasso' or 'ridge'")

# ベイズ最適化によるハイパーパラメータチューニング
opt.fit(X_scaled, y_scaled)
best_alpha = opt.best_params_['alpha']
model = opt.best_estimator_

# 回帰モデルのフィッティング
model.fit(X_scaled, y_scaled)

# 標準偏回帰係数を取得
coefficients = np.append(model.intercept_, model.coef_)

# 定数項を追加した説明変数のカラム名を作成
columns_with_const = ['Intercept'] + list(X.columns)

# 標準偏回帰係数をCSVとして出力
results_df = pd.DataFrame({
    'Variable': columns_with_const,
    'Standardized Coefficients': coefficients,
})
results_df.to_csv('standardized_coefficients.csv', index=False)

# VIFの計算のための関数
def calculate_vif(X):
    vif = pd.DataFrame()
    vif['Feature'] = X.columns
    vif['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
    return vif

# 定数項を除いたVIFを計算
X_scaled_no_const = pd.DataFrame(X_scaled, columns=X.columns)
vif_data = calculate_vif(X_scaled_no_const)

# VIFをCSVとして出力
vif_data.to_csv('vif.csv', index=False)

# 説明変数間の相関係数を計算
correlation_matrix = pd.DataFrame(X_scaled_no_const).corr()

# 相関係数マトリクスをCSVとして出力
correlation_matrix.to_csv('correlation_matrix.csv')

# クロスバリデーションによる決定係数の評価
if len(y) <= 10:
    cv = LeaveOneOut()
else:
    cv = KFold(n_splits=10, shuffle=True, random_state=1)

# クロスバリデーションで決定係数を評価
model_cv = model
scores = cross_val_score(model_cv, X_scaled, y_scaled, cv=cv, scoring='r2')

# Adjusted R²の計算
n = len(y)
p = X.shape[1]
adjusted_r2 = 1 - (1 - scores) * (n - 1) / (n - p - 1)
mean_adjusted_r2 = np.mean(adjusted_r2)

# Adjusted R²をCSVとして出力
adjusted_r2_df = pd.DataFrame({
    'Fold': range(1, len(adjusted_r2) + 1),
    'Adjusted R²': adjusted_r2
})
adjusted_r2_df.loc[len(adjusted_r2_df)] = ['Mean', mean_adjusted_r2]
adjusted_r2_df.to_csv('adjusted_r2.csv', index=False)

上記コードでは大きく5つのフローに分かれています。

  • データ前処理およびLasso or Ridge回帰のモデル選択
     
  • ハイパーパラメータの調整と回帰の実行
     
  • 標準編回帰係数の出力
     
  • 説明変数の相関係数の出力
     
  • VIFの出力
     
  • 自由度調整済み決定係数の出力

順に説明していきます。

スポンサーリンク

データ前処理およびLasso or Ridge回帰のモデル選択

データ前処理およびLasso or Ridge回帰のモデル選択は以下の部分で実行しています。

# CSVファイルの読み込み
data = pd.read_csv('california_housing_prices.csv')

# 説明変数と目的変数に分ける
X = data.iloc[:, 1:]
y = data.iloc[:, :1]

# データの標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
y_scaled = scaler.fit_transform(y.values.reshape(-1, 1)).flatten()

# 定数項を追加
X_scaled_with_const = add_constant(X_scaled)

# モデルの選択 ('lasso' または 'ridge')
model_type = 'lasso'  # ここを 'ridge' に変更することでRidge回帰を実行します

まずはCSVファイルのディレクトリを入力してCSVファイルを読み込み、列指定で目的変数は1列目、説明変数は2列目以降としてわけています。

次にデータの標準化として目的変数、説明変数ともにデータを標準化(min: 0, max: 1)しています。

最後に一応定数項を追加(ほぼ0ですが)して重回帰分析を実行しています。

モデルの選択ではmodel_typeの変数に’lasso’か’ridge’を代入することで、どちらの回帰手法で分析するかを選択することができます。

ハイパーパラメータの調整と回帰の実行

次にハイパーパラメータの調整と回帰の実行について説明します。

実行しているコードは以下の通りとなります。

# ベイズ最適化のための探索空間とガウス過程回帰の設定
search_spaces = {'alpha': Real(1e-4, 10e0, prior='log-uniform')}
kernel = RBF()
gp = GaussianProcessRegressor(kernel=kernel)

if model_type == 'lasso':
    # Lasso回帰のalphaをベイズ最適化する
    opt = BayesSearchCV(
        Lasso(), search_spaces, n_iter=50, cv=5, scoring='r2', n_jobs=-1, random_state=1,
        optimizer_kwargs={'base_estimator': gp, 'acq_func': 'EI'}
    )
elif model_type == 'ridge':
    # Ridge回帰のalphaをベイズ最適化する
    opt = BayesSearchCV(
        Ridge(), search_spaces, n_iter=50, cv=5, scoring='r2', n_jobs=-1, random_state=1,
        optimizer_kwargs={'base_estimator': gp, 'acq_func': 'EI'}
    )
else:
    raise ValueError("model_type should be either 'lasso' or 'ridge'")

# ベイズ最適化によるハイパーパラメータチューニング
opt.fit(X_scaled, y_scaled)
best_alpha = opt.best_params_['alpha']
model = opt.best_estimator_

# 回帰モデルのフィッティング
model.fit(X_scaled, y_scaled)

Lasso or Ridge回帰のハイパーパラメータとしては正則化項の係数alphaが該当します。

ハイパーパラメータは実行者があらかじめ数値を設定しなければいけないのですが、今回はこのalphaについてベイズ最適化による自動調整を行なっています。

ベイズ最適化については以下の記事にまとめてありますので、参照いただければと思います。

まずは1-4行目でガウス過程回帰の設定をしており、デフォルトではalphaの探索範囲は $1.0^{-4}$ ~ $10$ までとなっており、カーネルはRBFを使用しています。

6-24行目では選択したモデルにて実際にalphaを最適化するようにベイズ最適化を実行しています。

引数について全ては説明しませんが、探索範囲に対して50回探索を実行し、クロスバリデーションによる $r^2$ 値から最も予測性能の良いalphaを探索するようにしています。また、獲得関数はEIを使用しています。

最後に27行目で最適化したモデルにて、フィッティングを行なっています。

標準編回帰係数の出力

標準編回帰係数とt値の出力ですが、以下の部分で処理しています。

# 標準偏回帰係数を取得
coefficients = np.append(model.intercept_, model.coef_)

# 定数項を追加した説明変数のカラム名を作成
columns_with_const = ['Intercept'] + list(X.columns)

# 標準偏回帰係数をCSVとして出力
results_df = pd.DataFrame({
    'Variable': columns_with_const,
    'Standardized Coefficients': coefficients,
})
results_df.to_csv('standardized_coefficients.csv', index=False)

まずはLasso or Ridge回帰をインスタンス化したmodelについて標準編回帰係数を取得しています。

次にresults_dfとして先ほど取得した標準編回帰係数について変数をインデックスとしてデータフレームを作成しています。

最後にCSVとして出力しています。

VIFの出力

VIFの出力については以下の部分で処理しています

# VIFの計算のための関数
def calculate_vif(X):
    vif = pd.DataFrame()
    vif['Feature'] = X.columns
    vif['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
    return vif

# 定数項を除いたVIFを計算
X_scaled_no_const = pd.DataFrame(X_scaled, columns=X.columns)
vif_data = calculate_vif(X_scaled_no_const)

# VIFをCSVとして出力
vif_data.to_csv('vif.csv', index=False)

VIFの計算についてはまず、calculate_vif()という計算用の関数を作成しています。引数を説明変数のデータが格納されたデータフレームとしており、それぞれの変数が他の変数でどれだけ説明されるかを計算し、VIFを計算するようになっています。

この関数を使用してVIFを計算し、CSVとして出力しています。

変数間の相関係数の出力

変数間の相関係数の出力については以下の部分で処理しています。

# 説明変数間の相関係数を計算
correlation_matrix = pd.DataFrame(X_scaled_no_const).corr()

# 相関係数マトリクスをCSVとして出力
correlation_matrix.to_csv('correlation_matrix.csv')

相関係数は定数項を除いた変数で作成したデータフレームに対して.corr()メソッドを用いて計算しています。

最後に相関係数をマトリクスとしてCSVで出力しています。

決定係数の出力

決定係数の出力は以下の部分で処理しています。

# クロスバリデーションによる決定係数の評価
if len(y) <= 10:
    cv = LeaveOneOut()
else:
    cv = KFold(n_splits=10, shuffle=True, random_state=1)

# クロスバリデーションで決定係数を評価
model_cv = model
scores = cross_val_score(model_cv, X_scaled, y_scaled, cv=cv, scoring='r2')

# Adjusted R²の計算
n = len(y)
p = X.shape[1]
adjusted_r2 = 1 - (1 - scores) * (n - 1) / (n - p - 1)
mean_adjusted_r2 = np.mean(adjusted_r2)

# Adjusted R²をCSVとして出力
adjusted_r2_df = pd.DataFrame({
    'Fold': range(1, len(adjusted_r2) + 1),
    'Adjusted R²': adjusted_r2
})
adjusted_r2_df.loc[len(adjusted_r2_df)] = ['Mean', mean_adjusted_r2]
adjusted_r2_df.to_csv('adjusted_r2.csv', index=False)

今回、決定係数の算出はクロスバリデーションにより行なっており、データ数が10より少なければLeaveOneOutクロスバリデーションで10以上であれば10Foldクロスバリデーションを行ない、平均化した決定係数を出力します。

クロスバリデーションについては以下の記事にまとめてありますので、興味ある方はご参考ください。

また、決定係数について基本的に説明変数の数が多くなると1に近づいてしまうため、自由度調整済み決定係数としてadjust_r2を計算しています。

最終的には、クロスバリデーションで求めたそれぞれのFoldにおける決定係数とその平均値がCSVとして出力されるようになっています。

終わりに

以上がCSVを入力するだけでLasso ro Ridge回帰を実行し、結果を出力するコードの説明になります。LassoやRidge回帰は寄与が少ない変数を0に近づけてくれますので、重回帰分析で過学習になって予測性能が低くなってしまう場合に使用したい手法ですね。今後も簡単にデータ分析が実行できるコードを実装していきたいと思います。

スポンサーリンク
タイトルとURLをコピーしました