SEワンタンの独学備忘録

IT関連の独学した内容や資格試験に対する取り組みの備忘録

【機械学習】入門⑤ 線形基底関数モデル Pythonで学ぶ「教師あり学習」 回帰編

久しぶりの機械学習。

データセット

Boston house-prices (ボストン住宅価格データセット)

本題に入る前にデータセットについて。
毎回題材に合いそうなデータセットを用意するのも難しい場合があるので、公開されているデータセットを活用してみます。

scikit-learnのBoston house-prices (ボストン住宅価格データセット)と言われているものを使用します。
この他にもいくつか公開されているようです。

7. Dataset loading utilities — scikit-learn 0.22.1 documentation

データの中身は以下のような内容になっているようです。

■データ

カラム名 説明
CRIM 人口 1 人当たりの犯罪発生数
ZN 25,000 平方フィート以上の住居区画の占める割合
INDUS 小売業以外の商業が占める面積の割合
CHAS チャールズ川によるダミー変数 (1: 川の周辺, 0: それ以外)
NOX NOx の濃度
RM 住居の平均部屋数
AGE 1940 年より前に建てられた物件の割合
DIS 5つのボストン市の雇用施設からの距離
RAD 環状高速道路へのアクセスしやすさ
TAX $10,000 ドルあたりの不動産税率の総計
PTRATIO 町毎の児童と教師の比率
B 町毎の黒人 (Bk) の比率を次の式で表したもの。 1000(Bk – 0.63)^2
LSTAT 給与の低い職業に従事する人口の割合 (%)
MEDV 所有者が占有している家屋の$ 1000単位の中央値

全14カラムのうち、最初の13カラムが説明変数、最後のMEDVが目的変数となります。
実際にデータを取得してプロットしてみます。

・例

import numpy as npy
import matplotlib.pyplot as plt
%matplotlib inline

#ボストン住宅価格データセットの読み込み
from sklearn.datasets import load_boston

boston = load_boston()

# 説明変数
X_array = boston.data
# 1カラム目(CRIM)を取得
X=X_array[:,0]
# 目的変数
y_array = boston.target
Y=y_array

plt.figure(figsize=(4,4))
plt.plot(X,Y,marker='o',linestyle='None',markeredgecolor='black',color='cornflowerblue')

f:id:wantanBlog:20200215005022p:plain

今回は説明変数として「CRIM」のみを使用しますのであえて取り出しています。
本来はデータセットまるごと使用するものでしょう。

最小二乗法

まずは上記の「CRIM」と「MEDV」の相関関係をこれまでも行った最小二乗法によって導出します。


・使用データを絞る

import numpy as npy
import matplotlib.pyplot as plt
%matplotlib inline

#ボストン住宅価格データセットの読み込み
from sklearn.datasets import load_boston

boston = load_boston()

#説明変数
X_array = boston.data
# 1カラム目(CRIM)を取得
X=X_array[:,0]
#目的変数
y_array = boston.target
Y=y_array

plt.figure(figsize=(4,4))
# 0番目から15番目のデータをプロット
plt.plot(X[0:15],Y[0:15],marker='o',linestyle='None',markeredgecolor='black',color='cornflowerblue')

・出力結果
f:id:wantanBlog:20200215010433p:plain

これらのデータから線形回帰モデルとなる直線を以前と同様の方法で求めます。

・ライブラリによる最小二乗法

# sklearn.linear_model.LinearRegression クラスを読み込み
from sklearn import linear_model
import pandas as pd
import numpy as npy
import matplotlib.pyplot as plt
#ボストン住宅価格データセットの読み込み
from sklearn.datasets import load_boston

clf = linear_model.LinearRegression()

boston = load_boston()

#説明変数
X_array = boston.data
#目的変数
y_array = boston.target

X = npy.array([X_array[:,0][0:15]]).T
Y = y_array[0:15]

# 予測モデルを作成
clf.fit(X, Y)
# 回帰係数
print(clf.coef_)
# 切片 (誤差)
print(clf.intercept_)
# 決定係数
print(clf.score(X, Y))

# 散布図
plt.scatter(X, Y)
# 回帰直線
plt.plot(X, clf.predict(X))

・出力結果

[-16.03069468]
26.562651256893634
0.23087204323047003

f:id:wantanBlog:20200215011308p:plain


プロットの位置からも予想がつく通り、これらのデータから求められる直線は誤差が大きいです。
そして、犯罪率が高くなったとしても価格がマイナス値に振れるというのは現実的ではないでしょう。

線形基底関数モデル

線形基底関数モデル

これまでは線形回帰モデルは全て直線で考えてきました。
しかし現実のデータは当然ながら直線モデルで十分に表現できるものばかりではありません

そこで、今回は曲線モデルについて考えてみます。

数学の曲線というと二次関数、三次関数、対数関数、指数関数などがありますが、基底関数φ( x))はこれらのベースとなる関数のことを指しています。

線形基底関数モデルとは、線形回帰モデルのxを基底関数φ( x)で置き換えたものになります。

f:id:wantanBlog:20200215013639p:plain

ガウス基底関数

今回はガウス関数を基底関数としたガウス基底関数モデルを扱ってみます。

f:id:wantanBlog:20200217221223p:plain

ここで先にガウス関数の性質について軽く確認しておきましょう。

・ガウス関数

# ライブラリのインポート
import numpy as npy
import matplotlib.pyplot as plt
# Jupiter Notebookで結果を表示するためのおまじない
%matplotlib inline
# 関数の定義
def f1(x,mu,s):
    return npy.exp(-(x-mu)**2/(2*s**2))

# X軸に0から9までの配列
xline = npy.linspace(-10,10,500)
yline1 = f1(xline,0,1)
yline2 = f1(xline,1,1)
yline3 = f1(xline,0,2)

# pltに要素を設定する
plt.plot(xline, yline1, color ='black', label='$func1$')
plt.plot(xline, yline2, label='$func2$')
plt.plot(xline, yline3, label='$func3$')

# 凡例を表示する
plt.legend(loc="upper left")

# タイトルを表示する
plt.title('$gaussModel$')
# X軸に名前を付ける
plt.xlabel('$x$')
# Y軸に名前を付ける
plt.ylabel('$y$')
# グリッドを表示する
plt.grid(True)

# グラフを描写する
plt.show()

・出力結果

f:id:wantanBlog:20200217222426p:plain

形が分かると何となくイメージがつく気がしますが正規分布のモデルになります。
関数の性質としては「μ」が分布の位置(中央値)を表しており、「s」がグラフの幅を表していることが分かります。

ガウス関数を用いた線形基底関数モデル

ガウス関数を用いて線形基底関数モデルを考えていきます。

元々の線形回帰モデルは以下のようなものでした。

f:id:wantanBlog:20200217225413p:plain

xを基底関数Φに置き換えることにより線形基底関数モデルとして表現することができます。
以下はM=4の場合の線形基底関数モデルです。各関数Φはガウス関数を表します。

f:id:wantanBlog:20200217231650p:plain

線形基底関数モデルは重みパラメータである「w」を各基底関数に乗算し足し合わせたものになります。

w4」については他のパラメータと異なり関数Φがついていないため「Φ4(x)=1」というダミー関数をつけることで対応します。
これは前回にも適用した考え方です。

これにより線形基底関数yは以下のように表現することができます。

f:id:wantanBlog:20200218000058p:plain

このように表現できたことにより、平均二乗誤差も簡潔な表現をすることができます。

f:id:wantanBlog:20200218000123p:plain

また、パラメータwについても以前求めた解析解を置き換えることで基底関数モデルの場合でもwを求めることができます。

f:id:wantanBlog:20200218000642p:plain

ここでのΦ計画行列と呼ばれるもので以下を表しています。

f:id:wantanBlog:20200218002421p:plain

またこれは入力パラメータが複数の場合にも対応できる多次元入力モデルのベクトルxを用いた表現にするのが普通のようです。

f:id:wantanBlog:20200218003039p:plain

線形基底関数モデルの実装

全体像

最適なパラメータwを求めるためにPythonで実装を行います。
先に実装例を載せます。

・実装例

import numpy as npy
import matplotlib.pyplot as plt
%matplotlib inline

#ボストン住宅価格データセットの読み込み
from sklearn.datasets import load_boston

def gauss( x, mu, s):
    #return 1+s/(x+mu)
    return npy.exp(-(x-mu)**2 / (2*s**2))

def gauss_func(w,x):
    m = len(w) -1
    mu = npy.linspace(0,0.5,m)
    s = mu[1] -mu[0]
    y = npy.zeros_like(x)
    for j in range(m):
        y = y + w[j]*gauss(x, mu[j], s)
    y = y + w[m]
    return y

def mse_gauss_func(x, t, w):
    y = gauss_func(w, x)
    mse = npy.mean((y-t)**2)
    return mse

def fit_gauss_func(x,t,m):
    mu = npy.linspace(0,0.5,m)
    s = mu[1] - mu[0]
    n = x.shape[0]
    phi = npy.ones((n,m+1))
    for j in range(m):
        phi[:,j] = gauss(x,mu[j],s)
    
    phi_T = npy.transpose(phi)
    
    b = npy.linalg.inv(phi_T.dot(phi))
    c = b.dot(phi_T)
    w = c.dot(t)
    return w

def show_gauss_func(w):
    xb = npy.linspace(X_min, X_max, 30)
    y = gauss_func(w, xb)
    plt.plot(xb, y, c=[.5, .5, .5], lw=4)

boston = load_boston()

#説明変数
X_array = boston.data
#目的変数
y_array = boston.target

X = npy.array([X_array[:,0][0:40]])
Y = y_array[0:40]

xline = X[0]
yline = Y

X_min = 0
X_max = max(max(X))
X_n = len(X[0])

plt.figure(figsize=(4,4))
M = 4
W = fit_gauss_func(xline,yline,M)
show_gauss_func(W)

plt.plot(xline,yline,marker='o',linestyle='None',markeredgecolor='black',color='cornflowerblue')
plt.xlim(X_min, X_max)

plt.grid(True)
mse = mse_gauss_func(X[0],Y[0],W)
print('W=' + str(npy.round(W,1)))
print("SD={0:.2f}cm".format(npy.sqrt(mse)))
plt.show()

・出力結果

W=[16.8 -5.9 -1.5 5.6 15.7]
SD=6.02cm

f:id:wantanBlog:20200218234053p:plain


以下では関数を一つづつ確認していきます。

ガウス関数

先ほども示したガウス関数です。
f:id:wantanBlog:20200218235444p:plain

def gauss( x, mu, s):
    #return 1+s/(x+mu)
    return npy.exp(-(x-mu)**2 / (2*s**2))
パラメータwを求める関数

f:id:wantanBlog:20200219000908p:plain

Jを最小にする最適なパラメータwを求めるための関数です。
実装中の変数「phi」は計画行列Φを表しています。
後半部分ではΦを転置させ行列の乗算を行うことで最終的にパラメータwを求めています。

ガウス関数の返却値はxの数に対応するベクトルを返却します。
「phi」は行列なので行ごとにガウス関数の返却値を格納しています。

def fit_gauss_func(x,t,m):
    mu = npy.linspace(0,0.5,m)
    s = mu[1] - mu[0]
    n = x.shape[0]
    phi = npy.ones((n,m+1))
    for j in range(m):
        phi[:,j] = gauss(x,mu[j],s)
    
    phi_T = npy.transpose(phi)
    
    b = npy.linalg.inv(phi_T.dot(phi))
    c = b.dot(phi_T)
    w = c.dot(t)
    return w
線形基底関数モデル

xとwを入力パラメータとしてyを返却する以下の式を表しています。
f:id:wantanBlog:20200218235555p:plain

実装中の「mu」はμを表し位置を0から0.5の間をm個に分割してyに加算していきます(総和を求める)。

def gauss_func(w,x):
    m = len(w) -1
    mu = npy.linspace(0,0.5,m)
    s = mu[1] -mu[0]
    y = npy.zeros_like(x)
    for j in range(m):
        y = y + w[j]*gauss(x, mu[j], s)
    y = y + w[m]
    return y
平均二乗誤差Jを求める関数

f:id:wantanBlog:20200219000439p:plain
式で表されている通り先ほど作成したgauss_funcを用いてyを求めている。

def mse_gauss_func(x, t, w):
    y = gauss_func(w, x)
    mse = npy.mean((y-t)**2)
    return mse
線形基底関数モデルを描写する関数

線形基底関数モデルを呼び出しグラフへの描写を行います。

def show_gauss_func(w):
    xb = npy.linspace(X_min, X_max, 30)
    y = gauss_func(w, xb)
    plt.plot(xb, y, c=[.5, .5, .5], lw=4)
出力結果は・・・

f:id:wantanBlog:20200218234053p:plain

概ね散布図に従ったグラフが描写できています。
正直深く分かっていない部分もあるのでパラメータの設定にかなり苦労しました。

単純な直線ではないのでかなりそれらしい形になっていると思います。これぐらいできると入力データがよければ実践的にも使用可能なレベルになってきたようにも感じます。



ほんとに難しくなってきた・・・

・今回のソース
python_dev/Supervised_learning5.ipynb at master · wantanblog/python_dev · GitHub