"BOKU"のITな日常

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

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

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

f:id:arakan_no_boku:20190707151819j:plain

 

回帰問題とは

 

回帰問題をざっくりいうなら、こんな感じかなと思います。

入力データと出力データの組から対応する規則を学んで、未知の入力データに対して適切な出力を生成・・つまり予測・・できるようにする

回帰問題には「線形回帰」と「非線形回帰」があって、今回は「線形回帰」をとりあげるのですが、理論的なことについては、個人的には以下のリンクの記事の内容程度を「ざっくり」理解しているレベルです(;^_^A。

gihyo.jp

gihyo.jp

なので、回帰とはなんぞや・・と、説明するのは遠慮して、リンクだけのせます。

 

今回やろうとしていること

 

さて。

そんな「ざっくり」程度の理解で何をしようとしているか?と言えば、scikit-learnのLinearRegressionクラスの使い方についての整理です。

機械学習の話で最初の方に必ずでてくる基本中の基本みたいなクラスなので、引数の与え方とか結果の評価の仕方とかの基本形を押さえておきたいな・・と。

で・・、ノイズの入ったランダムなデータを学習させて、ノイズが加わる前の「正解」を線形回帰で予測する形で3パターンほどやってみることにします。

サンプルとしては以下の3つです。

 

サンプル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

 

$$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 

 

全サンプル共通の補足事項について

 

各サンプルに、元に学習・予測を行って、予測結果を決定係数で評価してみます。

個別のサンプルのところで繰り返し書くと読みづらいので、共通した補足事項を先に書いておきます。

 

決定係数について

 

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

分類問題の場合は分類結果が正解か不正解かがはっきりしているので、Accuracy(正答率)など明確な評価基準があります。

arakan-pgm-ai.hatenablog.com

でも、それは回帰問題には適用できなくて、かといって、なんとなく予測した数値をグラフで見て「何となくいけてそうだな」ですませるわけにもいかないので、客観的な指標として「決定係数」を使うというわけです。

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)

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

まあ、上記は内部でr2_scoreを呼び出しているだけらしいので当然ですが。

ちなみに「true_y」は正解、「predict_y」は予測結果となります。

 

学習と評価でエラーがでる場合があります

 

学習と評価には「scikit-learnのLinearRegression」を使います。

変数「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については、乱数を生成するところから、(200,1)のようにしてやっていますが、サンプル2とサンプル3については、あえてエラーになる状態で元データを作って、reshapeするやり方でやってます。

ちょっと冗長ですけど、ご容赦ください。

 

サンプルの学習・評価をやってみます

 

さて。

上記の各サンプルの学習・評価をやってみます。

結果はグラフで表示(予測した部分は赤色)しています。

でも、データ生成・学習・評価・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()

決定係数は、どちらの方法も同じ値を表示してました。

0.9958683658509848
0.9958683658509848

 

サンプル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)

表示された決定係数です。 

0.9996400797079943
0.9996400797079943 

 

サンプル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()

決定係数です。 

0.9928328334546288
0.9928328334546289

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

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]

]

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

 

最後に全ソースです

 

線形回帰は上記の3パターン覚えてたら、なんとなく応用はきくと思ってます。

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

サンプル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()

今回はこのへんで。

ではでは。