"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

3次式の線形回帰問題をPythonで。最小二乗法とRidge回帰とLasso回帰などの使い分けサンプル。

今回は3次式の線形回帰問題をPythonでやってみます。

ついでに、同じ問題に、LinearRegression以外の罰則付き回帰の実装であるRidge回帰とLasso回帰も適用して、使い分け方などをメモしておこうかと思ってます。

f:id:arakan_no_boku:20190707151819j:plain 

 

今回のお題と前提について

 

前回、2次式の単回帰や変数が複数ある重回帰をLinearRegressuonクラスを使ってやってみました。

arakan-pgm-ai.hatenablog.com

それを受けて、今回は以下のような一つの変数が式に複数回でてくる3次式をやってみます。

$$3x^3-2x+3$$

変数Xを-2から2の範囲の乱数で発生させて、上記式で計算した結果を正解とします。

ただ、そのままだと面白くないので、学習データにはそれに-20から20の大きな範囲で発生させた乱数をノイズとして加えます。

そのノイズ付きのデータを学習させることで、どの程度ノイズを加える前の正解の式に近いものを導けるか?をやります。

ノイズを加えた後の学習データのグラフは例えば、こんな感じになります。

f:id:arakan_no_boku:20190712150659p:plain

良い感じにばらけてますね。

さらに。

今回は、ちゃんと学習データと予測元データを分け、ちゃんと汎化性能も確認します。

あと、LinearRegression以外に、罰則付き回帰のRidge回帰とLasso回帰・ElasticNetも試します。

罰則付き回帰について、ちょっとだけ補足です。

よく利用されるLinearRegressionクラスは学習時に正解の値と予測値の差のみに着目して、それを最小にしていく考え方の「最小二乗法」を使っています。

mathtrain.jp

それに対して、数値の誤差に加えて、モデルの複雑度を加味するようにしたのが、罰則付き回帰という方法です。

考え方としては同じですが、モデルの複雑度を反映させる方法の違いで「Ridge回帰」・「Lasso回帰」・「ElasticNet」の種類があるということです。

jojoshin.hatenablog.com

まあ、偉そうにいっても、正直、自分は上記の数学的な部分については、ごく「ざっくり」としか理解できていません(笑)

でも、経験的に。

LinearRegressionで過学習傾向がでたりした時に、罰則付き回帰を適用すると、改善されることがある・・ということは知っています。

なので、一応、引き出しのひとつとして、使い方とかは知っておくと役にたつことがあるだろうと思っています。

 

まずLinearRegressionクラス

 

最初にソースです。

sk_linear_sample04.py

import matplotlib.pyplot as plt
import numpy as np
from sklearn import linear_model
from sklearn.metrics import r2_score

# 変数2つを、-2から2の範囲の乱数で初期化
x = np.random.rand(300, 1) * 4 - 2
# ノイズはー5から5の範囲の乱数で生成する
noise = np.random.rand(300, 1) * 40 - 20
# 正解をもとめる y=3x - 2z -3
true_y = 3 * x ** 3 - 2 * x + 3
# ノイズを加える
y = true_y + noise
# 学習用
_x_train = x[:200]
y_train = y[:200]
# 予測用
_x_test = x[200:]
true_y_test = true_y[200:]
# 引数に使うため、2変数をまとめる
x_train = np.c_[_x_train**3, _x_train]
x_test = np.c_[_x_test**3, _x_test]
# クラスオブジェクトを生成
model = linear_model.LinearRegression()
# 学習する
model.fit(x_train, y_train)
# 予測する
predict_y = model.predict(x_test)
# グラフに書くための設定
plt.scatter(_x_train, y_train, marker="+", color="b")
plt.scatter(_x_test, predict_y, color="r")

# 決定係数を求める
r2_score = r2_score(true_y_test, predict_y)
print(r2_score)
# グラフを描画する
plt.show()

少し補足します。

学習用と予測用に元データを分ける時に以下のようにしています。

#学習用
_x_train = x[:200]
y_train = y[:200]
#予測用
_x_test = x[200:]
true_y_test = true_y[200:]

 学習の時にはノイズ月の_y、予測時には予測値と比較するためにノイズなしの_true_yしか使わないので、こういう風にしているわけです。

あと、この部分。

x_train = np.c_[_x_train**3,_x_train]
x_test = np.c_[_x_test**3,_x_test]

 変数は一つなのですが、式の中で2か所で使っている場合は、複数変数の重回帰分析時と同じように、上記のような与え方で一本化してやる必要があるので注意です。

さて。

実行した結果はこんな感じでした。

f:id:arakan_no_boku:20190712161225p:plain

ノイズがひどかった割には、比較的に元の式に近い曲線を導いている気はします。

ちなみに、この時の決定係数は

0.9798807747077892

でした。

 

今度は罰則付き回帰を適用してみる

 

さて。

今度は罰則付き回帰であるRidge回帰とLasso回帰・ElasticNetを試します。

これは、scikit-learnを使う前提なら、非常に簡単に試せます。

ソースとしては、上記のLinearRegressionのものとほぼ同じでいいです。

書き換えるのはクラスオブジェクトを生成する一か所だけです。

LinearRegressionの場合はこうです。

model = linear_model.LinearRegression()

それを。

Redge回帰にするとき。

model = linear_model.Ridge()

Lasso回帰にするとき。

 model = linear_model.Lasso()

ElasticNetのときはこう

model = linear_model.ElasticNet()

書き換えるだけで試せます。

なので、個別のソースは示さず、実行結果のグラフと決定係数だけをのせておきます。

実行都度、違う乱数が発生するようになっているので、決定係数を比較しても意味はありませんが、今回の例程度の複雑度では、結果に大きな違いはないみたいです。 

実際、データの性質や学習データの量などの条件を踏まえて、どの手法を採用するのかについては、個別ケースで検討すべきなのでしょうね。

 

罰則付き回帰の結果です

 

以下の順番でいきます。

  1. Ridge回帰
  2. Lasso回帰
  3. ElasticNet
Ridge回帰

sk_ridge_sample04.py

f:id:arakan_no_boku:20190712171509p:plain

決定係数は

0.9546429119511959

でした。

 

Lasso回帰

 

sk_lasso_sample04.py

f:id:arakan_no_boku:20190712171741p:plain

決定係数は

0.9735932177856357

でした。

 

ElasticNet

 

sk_elastic_sample04.py

f:id:arakan_no_boku:20190712172026p:plain

決定係数は

0.9631156876487091

でした。

前にも書きましたが、ホント、この程度のデータだと違いがわからないですねえ。

が・・、それがわかった・・というのも立派な情報ですから(笑)

今回はこんなところで。

ではでは。