"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

機械学習の線形回帰問題をPythonで。scikit-learnのLinearRegressionクラスのサンプル

f:id:arakan_no_boku:20190707151819j:plain

目次

機械学習の線形回帰問題

線形回帰問題をPythonで解く「scikit-learn」の「LinearRegression」クラスの使い方と決定係数での評価について、基本的なところを、おさらいしておこうかと思います。

入力データと出力データの組から対応する規則を学んで、未知の入力データに対し適切な出力をえる(つまり予測)できるようにするのが「回帰問題」です。

回帰問題には「線形回帰」と「非線形回帰」があります。

線形回帰と非線形回帰の基本的な違いは、モデルが取り得る関数の形式です。

このへんは線形・非線形の名前でなんとなくはわかります。

線形回帰では線形パラメータが必要で、非線形回帰では必要ないとか理論的な話はありますが、とりあえず趣味でやってるので、なんとなくイメージできたら、とりあえずプログラムを動かして理解してみよう・・というスタンスでやってみます。

まずは学習用のサンプルデータを作成

ノイズの入ったランダムなデータを作成します。

それを学習して、ノイズが加わる前の「正解」を線形回帰で予測する形を3パターンほどやってみることにします。

サンプルデータ1:単回帰(変数が1つ)の一次式用

$$y=3x-1$$ の式で「-2 から 2」までの乱数をXとして計算したものです。

単回帰(変数が1つ)の一次式の場合の例です。

その結果にノイズとして「-5 から 5」の間の乱数を加算してばらけさせてます。

これを学習して、正解にどれくらい近い式を導けるかを試します。

sk_linear_sample01.py

f:id:arakan_no_boku:20190710170705p:plain

乱数を発生させる部分のソースです。

x = np.random.rand(200, 1) * 4 -2
noise = np.random.rand(200, 1) * 10 -5
true_y = x * 3 - 1 
y = true_y + noise

ちなみに 「np.random.rand()」が、0.0から1.0の乱数を返すので、それい4をかけて、2を引くことで、無理やり「-2から2」の乱数にするという方法をつかってます。

サンプルデータ2 :単回帰(変数がひとつ)の二次式用

$$y  = 3x^2  + 1$$の式で「-2 から 2」までの乱数をXに計算したものです。

単回帰(変数がひとつ)の二次式の場合の式になります。

その結果にノイズとして「-5 から 5」の間の乱数を加算してばらけさせてます。

これを学習して、正解にどれくらい近い式を導けるかを試します

sk_linear_sample02.py

f:id:arakan_no_boku:20190710171713p:plain

上記の元データを発生させる部分のソースです。

x = np.random.rand(300, 1) * 4 -2
noise = np.random.rand(300, 1) * 10 -5
true_y = 3 * x**2 + 1
y = true_y + noise
サンプルデータ3:重回帰(変数が複数・・今回は2つ)用

$$y=3x -2z  -3$$ の式で「-2 から 2」までの乱数をXとして計算したものです。

重回帰(変数が複数・・今回は2つ)の例です。

その結果にノイズとして「-5 から 5」の間の乱数を加算してばらけさせてます。

これを学習して、正解にどれくらい近い式を導けるかを試します。

なお、グラフは変数2つだと3Dにした方が良いのですが、平面のグラフで表現するために、変数「x」と[z」それぞれのグラフで表現しています。

sk_linear_sample03.py

f:id:arakan_no_boku:20190710173303p:plain

上記の元データを発生させるソースです。

x = np.random.rand(300, 1) * 4 -2
z = np.random.rand(300, 1) * 4 -2
noise = np.random.rand(300, 1) * 10 -5
true_y = 3 * x - 2 * z - 3
y = true_y + noise 

scikit-learnのLinearRegressionクラスで学習・評価

学習と評価には、scikit-learnのLinearRegressionクラスを使ってみます。

LinearRegressionの和訳は「線形回帰」です。

まさにどんぴしゃですので。

結果はグラフで表示します。

元データに重ねて、予測値を赤色で表示します。

ソースは、データ生成・学習・評価・R2スコアの計算というメインの部分のみを記載しています。(グラフに表示する部分は最後にまとめて掲載します。 )

なお、今回はやり方の確認だけを目的として、横着して、学習データと予測元データに同じものを使っています。

かなり手抜きですが・・すいません。

サンプル1(単回帰一次式)

結果のグラフはこんな感じです。

f:id:arakan_no_boku:20190711100102p:plain

 ソースはこんな感じ。

sk_linear_sample01.py

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

# -2から2の範囲で乱数を生成する
x = np.random.rand(200, 1) * 4 - 2
# ノイズは-5から5の範囲で乱数を生成する
noise = np.random.rand(200, 1) * 10 - 5
# 正解のyを計算する y=3x-1
true_y = x * 3 - 1
# ノイズを加える
y = true_y + noise
# クラスオブジェクトの生成
model = linear_model.LinearRegression()
# 学習する
model.fit(x, y)
# 予測する
predict_y = model.predict(x)

plt.scatter(x, y, marker="+", color="b")
plt.scatter(x, predict_y, color="r")
# 決定係数を計算する方法1
r2 = model.score(x, true_y)
print(r2)
# 決定係数を計算する方法2
r2_score = r2_score(true_y, predict_y)
print(r2_score)
plt.show()
サンプル2(単回帰二次式)

結果のグラフはこんな感じです。 

f:id:arakan_no_boku:20190711102333p:plain

ソースはこんな感じ。

sk_linear_sample02.py 

import numpy as np
from sklearn import linear_model
from sklearn.metrics import r2_score

#-2から2の範囲で乱数を生成する
_x = np.random.rand(300) * 4 -2
#ノイズは-5から5の範囲で乱数を生成する
noise = np.random.rand(300) * 10 -5
#正解を求める y=3x**2+1
_true_y = 3 * _x**2 + 1
#ノイズを加える
_y = _true_y + noise
#エラー回避のため、reshapeする
x = np.reshape(_x,(-1, 1))
y = np.reshape(_y,(-1, 1))
true_y = np.reshape(_true_y,(-1, 1))
#クラスインスタンスを生成
model = linear_model.LinearRegression()
#学習する(xは二乗であたえる)
model.fit(x**2, y)
#予測する(xは二乗であたえる)
predict_y = model.predict(x**2)

#決定係数を求める方法1
r2 = model.score(x**2,true_y)
print(r2)
#決定係数を求める方法2
r2_score = r2_score(true_y,predict_y)
print(r2_score)
サンプル3(重回帰2変数)

結果のグラフです。

f:id:arakan_no_boku:20190711105256p:plain

うーん。

視覚的にはわかりづらいです。

ソースはこんな感じ。

sk_linear_sample03.py

import numpy as np
from sklearn import linear_model
from sklearn.metrics import r2_score

#変数2つを、-2から2の範囲の乱数で初期化
_x = np.random.rand(300) * 4 -2
_z = np.random.rand(300) * 4 -2
#ノイズはー5から5の範囲の乱数で生成する
noise = np.random.rand(300) * 10 -5
#正解をもとめる y=3x - 2z -3
_true_y = 3 * _x - 2 * _z - 3
#ノイズを加える
_y = _true_y + noise
#引数に使うため、2変数をまとめる
x_z = np.c_[_x,_z]
#エラー回避のためreshapeする
y = np.reshape(_y,(-1, 1))
true_y = np.reshape(_true_y,(-1, 1))
#クラスオブジェクトを生成
model = linear_model.LinearRegression()
#学習する
model.fit(x_z, y)
#予測する
predict_y = model.predict(x_z)

r2 = model.score(x_z,true_y)
print(r2)
r2_score = r2_score(true_y,predict_y)
print(r2_score)
plt.tight_layout()

 ソースをちょっとだけ補足します。

x_z = np.c_[_x,_z]

 の部分です。

重回帰(複数変数)の場合、変数が2つ以上あるのに、fitやpredictの引数はひとつしか与えられないので、変数を一本化する必要があるということです。

例えば

  • X=[0.72798292 0.40134744]
  • Z=[0.4321639 0.12507638]

だった場合、np.c_[X,Z] をすると、以下のように変換されます。

[

 [0.72798292 0.4321639 ]
 [0.40134744 0.12507638]

]

 これは便利なんですよね。

 

共通の補足事項:決定係数

各サンプルでは決定係数を計算しています。

決定係数というのは、予測精度の客観的な指標です。

bellcurve.jp

決定係数の求め方ですが、使われるやり方が2通りあります。

linear_model.LinearRegressionクラスのscoreメソッドを使う方法がひとつ。

r2 = model.score(x,true_y)

もうひとつは、sklearn.metrics.r2_scoreを使う方法です。 

from sklearn.metrics import r2_score

r2_score = r2_score(true_y,predict_y)

どちらも得られる結果は同じです。

ちなみに「true_y」は正解、「predict_y」は予測結果となりますが、学習データと予測元データに同じものを使っているので上記の結果は0.99を超えてます(笑)。

普通は、0.5を超えたらまあまあ・・くらいの目安らしいのですけどね。

 

共通の補足事項:ありがちなエラー

変数「x」「y」で学習評価する場合の基本形はこんな感じです。

from sklearn import linear_model
#クラスオブジェクト生成
model = linear_model.LinearRegression()
#学習の実行
model.fit(x, y)
#評価の実行
predict_y = model.predict(x)

model.fitやmodel.predictに渡す時に、以下のようなエラーがでるときがあります。 

ValueError: Expected 2D array, got 1D array instead:

これは、たとえば以下のように次元を明示しないで生成したようなデータとかがはいってきた時に発生します。

x = np.random.rand(200) * 4 -2

この例だと、本来以下のように、shapeを指定してなければいけないところ、されていないので次元があっていないわけです。

x = np.random.rand(200,1) * 4 -2

まあ、この例のように自分でデータを生成しているのなら、発生元を直せばいいのですが、そうでなくて外部からデータを受け取る場合もあるので、その場合は、model.fit等に渡す前にreshapeしてやる必要があります。

たとえば、上記の例だと。

_x = np.random.rand(200) * 4 -2
noise = np.random.rand(200) * 10 -5
_y = _x * 3 - 1  + noise
x = np.reshape(_x,(-1, 1))
y = np.reshape(_y,(-1, 1))

model = linear_model.LinearRegression()
model.fit(x, y)
predict_y = model.predict(x) 

こんな感じです。

 

最後に全ソースです

上記ではグラフを描く部分をはしょったので、最後に全ソースを掲載しておきます。

サンプル1

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

#-2から2の範囲で乱数を生成する
x = np.random.rand(200,1) * 4 -2
#ノイズは-5から5の範囲で乱数を生成する
noise = np.random.rand(200,1) * 10 -5
#正解のyを計算する y=3x-1
true_y = x * 3 - 1 
#ノイズを加える
y = true_y + noise
#クラスオブジェクトの生成
model = linear_model.LinearRegression()
#学習する
model.fit(x, y)
#予測する
predict_y = model.predict(x)

plt.scatter(x,y,marker="+",color="b")
plt.scatter(x,predict_y,color="r")
#決定係数を計算する方法1
r2 = model.score(x,true_y)
print(r2)
#決定係数を計算する方法2
r2_score = r2_score(true_y,predict_y)
print(r2_score)
plt.show()

サンプル2 

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

# -2から2の範囲で乱数を生成する
_x = np.random.rand(300) * 4 - 2
# ノイズは-5から5の範囲で乱数を生成する
noise = np.random.rand(300) * 10 - 5
# 正解を求める y=3x**2+1
_true_y = 3 * _x**2 + 1
# ノイズを加える
_y = _true_y + noise
# エラー回避のため、reshapeする
x = np.reshape(_x, (-1, 1))
y = np.reshape(_y, (-1, 1))
true_y = np.reshape(_true_y, (-1, 1))
# クラスインスタンスを生成
model = linear_model.LinearRegression()
# 学習する(xは二乗であたえる)
model.fit(x**2, y)
# 予測する(xは二乗であたえる)
predict_y = model.predict(x**2)

plt.scatter(x, y, marker="+", color="b")
plt.scatter(x, predict_y, color="r")
# 決定係数を求める方法1
r2 = model.score(x**2, true_y)
print(r2)
# 決定係数を求める方法2
r2_score = r2_score(true_y, predict_y)
print(r2_score)
plt.show()

サンプル3 

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) * 4 - 2
_z = np.random.rand(300) * 4 - 2
# ノイズはー5から5の範囲の乱数で生成する
noise = np.random.rand(300) * 10 - 5
# 正解をもとめる y=3x - 2z -3
_true_y = 3 * _x - 2 * _z - 3
# ノイズを加える
_y = _true_y + noise
# 引数に使うため、2変数をまとめる
x_z = np.c_[_x, _z]
# エラー回避のためreshapeする
y = np.reshape(_y, (-1, 1))
true_y = np.reshape(_true_y, (-1, 1))
# クラスオブジェクトを生成
model = linear_model.LinearRegression()
# 学習する
model.fit(x_z, y)
# 予測する
predict_y = model.predict(x_z)

plt.subplot(1, 2, 1)
plt.scatter(_x, y, marker="+", color="b")
plt.scatter(_x, predict_y, color="r")
plt.xlabel('x')
plt.subplot(1, 2, 2)
plt.scatter(_z, y, marker="+", color="b")
plt.scatter(_z, predict_y, color="r")
plt.xlabel('z')
r2 = model.score(x_z, true_y)
print(r2)
r2_score = r2_score(true_y, predict_y)
print(r2_score)
plt.tight_layout()
plt.show()

今回はこのへんで。

ではでは。