"BOKU"のITな日常

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

scikit-learnのSVMとtf.kerasのシンプルなモデルで簡単な非線形回帰問題をやってみる

非線形回帰問題のサンプルとして、Sinカーブの予測をScikit-learnのサポートベクターマシンSVM)と、kerasのシンプルな全結合のみのモデルで試しにやってみます。

f:id:arakan_no_boku:20190707151819j:plain

 

非線形回帰問題

 

線形回帰については、以下の記事でも参考のリンクを紹介したりしてます。

arakan-pgm-ai.hatenablog.com

でも、非線形回帰についての同様の資料はなかなか見つかりません。

たぶん。

非線形回帰問題というのは「線形ではない回帰問題」なので、漠然と範囲が広すぎるんでしょうかね?

それに「線形」と「非線形」の使い分けも、きちんと決められるものではなくて、解こうとしている問題が線形回帰のアプローチではうまくいきそうもないなら・・、非線形回帰の手法をあてはめてみる・・みたいな感じみたいなのですね。

うーーん。

 

今回のネタはSinカーブの予測です

 

ということで。 

今回のネタは「Sinカーブ」の予測です。

データが作りやすいというだけの理由で選びました。

例によって、乱数で作ってノイズをのせる方法で学習データをつくって、それを使って、どのくらいの精度で予測できるようになるか・・をやってみます。

データを作る部分のソースです。

import numpy as np
import math

# 変数を、-10から10の範囲の乱数で初期化
x = np.random.rand(1000, 1) * 20 - 10
# ノイズはー5から5の範囲の乱数で生成する
noise = np.random.rand(1000) * 4 - 2
# 正解をもとめる y=3x - 2z -3
true_y = np.array([math.sin(v) for v in x])
# ノイズを加える
y = true_y + noise
# 学習用
x_train = x[:700]
y_train = y[:700]
# 予測用
x_test = x[700:]
true_y_test = true_y[700:]

-10から10の範囲で1000個の乱数を生成して、それを元にSin()カーブを計算します。

その結果に-2から2の範囲で生成したノイズを加えたものをデータにします。

そのデータを学習用に700、予測評価用に300に分割します。

グラフにするとこんな感じです。

f:id:arakan_no_boku:20190715215133p:plain

この位の複雑度になると線形回帰(LinearRegression」では対応できないのを確かめるために、やってみました。

f:id:arakan_no_boku:20190715215313p:plain

全然駄目ですね(笑)

今回のデータが線形回帰の手法では予測の精度をあげるのは難しいというのは、はっきりとしました。

 

サポートベクターマシーン(SVM)でやってみる

 

ということで。

非線形回帰の道具として、サポートベクターマシンSVM)を使ってみます。

ここで、SVMとは・・なんて説明できればカッコいいのですが、残念ながら、自分はサポートベクタマシンのアルゴリズムをきちんと理解できてはいません。

datachemeng.com

でも、Scikit-learnのSVMの使い方は知っています。

かつ、SVMが分類だけでなく回帰でも利用できて、こういう非線形回帰問題に対して、比較的良い結果(精度)をだす手法であるということも知ってます。

なので、やってみます。

グダグダですいません。

まず、ソースです。

短いので、前に書いたデータ生成部とグラフに表示する部分も含めた全体を貼り付けました。

sk_svm_sample01.py

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

# 変数を、-10から10の範囲の乱数で初期化
x = np.random.rand(1000, 1) * 20 - 10
# ノイズはー5から5の範囲の乱数で生成する
noise = np.random.rand(1000) * 4 - 2
# 正解をもとめる y=3x - 2z -3
true_y = np.array([math.sin(v) for v in x])
# ノイズを加える
y = true_y + noise
# 学習用
x_train = x[:700]
y_train = y[:700]
# 予測用
x_test = x[700:]
true_y_test = true_y[700:]
# クラスオブジェクトを生成
model = svm.SVR(gamma="auto")
# 学習する
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()

ちょっと補足します。

クラスオブジェクトの生成の部分で以下のようにしてます。

model = svm.SVR(gamma="auto")

バージョン0.21.1では「gamma="auto"」がデフォルトなので、以下のようにしても同じ結果になります。

model = svm.SVR()

だから、ちょっと冗長なのですが、実は、バージョン0.22からは、このデフォルトが変わる予定らしいのですね。

そのため、gammaを明示しないで、svmでクラスオブジェクトを生成すると、以下のような警告がでることもあります。

FutureWarning: The default value of gamma will change from 'auto' to 'scale' in version 0.22 to account better for unscaled features. Set gamma explicitly to 'auto' or 'scale' to avoid this warning."avoid this warning.", FutureWarning)

この警告の意味は、将来のバージョン(0.22)から、初期値が「auto」から「scale」に変更になるということなので、警告を消して、将来的な誤動作を回避するために、以下のようにgammaを明示的に指定しているわけです。

ちなみに。

今回のデータだと、gamma="scale”にすると、かなり結果の精度は落ちました。

さて。

これで実行してみます。

結果のグラフです。

f:id:arakan_no_boku:20190716203115p:plain

ちょっと、いびつですが、Sinカーブっぽく予測してます。

決定係数は

0.8442315356575069

処理時間は速いですし、特に凝ったこともせず一発勝負でやった割には、まあまあじゃないですかね。

 

試しにニューラルネットワークモデルでやってみる

 

非線形の例としては、SVMだけでもいいかなと思ったのですが、丁度、tensorflowの2.0betaをインストールした環境だったので、ごく簡単なニューラルネットワークモデルでもやってみます。

www.tensorflow.org

このtensorflow2.0の回帰問題のチュートリアルを参考にモデルを組んでみました。

www.tensorflow.org

ソースはこんな感じです。

sk_tf_dense_sample01.py

import matplotlib.pyplot as plt
import numpy as np
from sklearn import svm
from sklearn.metrics import r2_score
import math
import tensorflow as tf

# 変数を、-10から10の範囲の乱数で初期化
x = np.random.rand(1000, 1) * 20 - 10
# ノイズはー5から5の範囲の乱数で生成する
noise = np.random.rand(1000) * 4 - 2
# 正解をもとめる y=3x - 2z -3
true_y = np.array([math.sin(v) for v in x])
# ノイズを加える
y = true_y + noise
# 学習用
x_train = x[:700]
y_train = y[:700]
# 予測用
x_test = x[700:]
true_y_test = true_y[700:]

# モデルを生成
model = tf.keras.Sequential([
    tf.keras.layers.Dense(64, activation='relu', input_shape=(1,)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1)
])

optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse'])
# 学習する
EPOCHS = 1000

model.fit(
    x_train,
    y_train,
    epochs=EPOCHS,
    validation_split=0.2,
    verbose=0,
    callbacks=[])

# 予測する
predict_y = model.predict(x_test).flatten()

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()

データの生成と決定係数の計算・グラフに描く部分はSVMのケースと同じです。

モデルはほぼチュートリアルを踏襲してます。

活性化関数だけ、tanhやsigmoidなどに変更して、いろいろ試してみたのですが、結局元の「relu」版が一番結果がよかったので戻してます。

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

f:id:arakan_no_boku:20190716204938p:plain

微妙・・ですが。

SVM同様、Sinカーブにそった予測はできているようです。

決定係数は、SVMの時より良くて

0.9428423451440056

でした。

一見、「さすが、ニューラルネットワークモデル」に見えますが・・・、1000エポック分、学習を繰り返すのでSVMの時より圧倒的に時間がかかりました。

といっても、1から2分程度・・の話なのですが。

たぶん、モデルをもっと複雑にしてチューニングしていけば、さらに学習に時間がかかるようになって、そのぶん精度はあがるのでしょうが、今回は非線形回帰問題の雰囲気を確認するサンプルなので、ザックリこんなもんでとめておきます。

今回はこんなところで。

ではでは。