"BOKU"のITな日常

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

Pythonで非線形回帰のサンプル/scikit-learnのSVM(サポートベクターマシン)とtf.keras(ニューラルネットワーク)

f:id:arakan_no_boku:20190707151819j:plain

目次

非線形回帰

正解付き(教師あり)データで学習して、特徴をつかみ、未知の入力に対して結果を予測するのが回帰問題です。

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

簡単に違いを書くと

  • 線形:グラフを描くと真っ直ぐな線になり、比例関係で、結果が予測しやすい。
  • 非線形:グラフを描くと曲線になる。比例関係でないので、結果を予測しにくい。

です。

今回は非線形回帰問題のサンプルとして、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

 

サポートベクターマシーン(SVM

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

残念ながら、自分はサポートベクタマシンのアルゴリズムをきちんと理解できてはいないのですが、SVMが回帰でも利用できて、非線形回帰問題に対して、比較的良い結果(精度)をだすということなので、とりあえず、プログラムを動かして、感覚で理解しようと思います。

datachemeng.com

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カーブっぽく予測してます。

ニューラルネットワークモデル

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

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カーブにそった予測はできているようなので、サンプルとしては、こんなもんでしょうか。

今回はこんなところで。

ではでは。