SEワンタンの独学備忘録

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

【機械学習】入門③ Pythonで学ぶ「教師あり学習」 回帰編(二次元入力のモデル)

前回まではX軸とY軸の二要素のみを扱ってきました。
二要素ならばそこまでがっつりやりこまなくてもなんとかなりましたが、あまり機械学習という感じもしませんでした。

今回は入力が二要素の場合です。最終的には多数要素を扱いますが、回帰モデルも単純な一次関数にはならないので少しそれっぽさがでてくるとともに難解になってくると思われます。

二次元入力の面モデル

データセットの準備

とにもかくにもデータセットを準備します。

今回も本ブログのアクセス関連のデータを使用したいと思います。
※データ自体はなんでもいいです。

・ユーザ数
・ページビュー数
・セッション数

・例

# データモデルの準備
import numpy as npy

# データ生成
xnum = 8
views =[235,375,568,931,1497,1542,3176,2155]
users =[24,93,154,370,746,868,1809,1308]
session =[103,221,306,539,1010,1127,2307,1587]

# データをblog_data.npzファイルに保存する
npy.savez('blog_data2.npz',X=users,X_min=min(users),X_max=max(users),X_n=len(users),Y=views,Z=session)

# データをblog_data.npzファイルから取り出す
sample_data = npy.load('blog_data2.npz')

print(sample_data['X'])
print(sample_data['Z'])

・出力結果

[  24   93  154  370  746  868 1809    1  308]
[60.19 68.78 71.24 75.88 81.19 85.54 84.44 84.94]

データセットは前回と同じように用意ができました。
今回はデータセットの時点で三次元となりイメージがややしにくいため、三次元の散布図化します。

・例

# データモデルの準備
import numpy as npy
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

def show_model(ax, X, Y, Z):
    for i in range(len(X)):
        ax.plot([X[i],X[i]],[Y[i],Y[i]],[50,Z[i]],color='gray')
        
    ax.plot(X,Y,Z,'o',color='cornflowerblue',markeredgecolor='black',markersize=6, markeredgewidth=0.5)
    ax.view_init(elev=35, azim=-75)


# データをblog_data2.npzファイルから取り出す
sample_data = npy.load('blog_data2.npz')

plt.figure(figsize=(6, 5))
ax = plt.subplot(1,1,1,projection='3d')
show_model(ax, sample_data['X'], sample_data['Y'], sample_data['Z'])
plt.show()

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


三次元の散布図プロットは上記のようになるんですね。
前回までで、ビュー数とユーザ数については明確に相関関係があることが分かっていますが、セッション数とどのような相関関係をもつのか見ていきます。

Pythonで面モデルを生成する

まずはPythonによる実装から入っていきます。
中身は後からみていきましょう。

・例

# データモデルの準備
import numpy as npy
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

# データプロット
def show_model(ax, X, Y, Z):
    for i in range(len(X)):
        ax.plot([X[i],X[i]],[Y[i],Y[i]],[50,Z[i]],color='gray')
        
    ax.plot(X,Y,Z,'o',color='cornflowerblue',markeredgecolor='black',markersize=6, markeredgewidth=0.5)
    ax.view_init(elev=35, azim=-75)

# 面を生成
def show_plane(ax, w):
    px =npy.linspace(X_min, X_max, 5)
    py =npy.linspace(Y_min, Y_max, 5)
    px, py = npy.meshgrid(px, py)
    y = w[0]*px + w[1]*py + w[2]
    ax.plot_surface(px, py, y, rstride=1,cstride=1,alpha=0.3,color='blue',edgecolor='black')

# 二乗誤差
def mse_plane(X,Y,Z,w):
    y = w[0]*X + w[1]*Y +w[2]
    mse = npy.mean((y - Z)**2)
    return mse

# データをblog_data2.npzファイルから取り出す
sample_data = npy.load('blog_data2.npz')
X=sample_data['X']
Y=sample_data['Y']
Z=sample_data['Z']
X_min = min(X)
X_max = max(X)
Y_min = min(Y)
Y_max = max(Y)

plt.figure(figsize=(6,5))
ax = plt.subplot(1,1,1,projection='3d')
W=[0.45,0.5,100]
show_plane(ax, W)
show_model(ax, X,Y,Z)
mse = mse_plane(X,Y,Z,W)
print("SD={0:.2f} cm".format(npy.sqrt(mse)))
plt.show()

・出力結果

f:id:wantanBlog:20200119175648p:plain

それらしい面が生成できました。
それらしい結果にするために今回は「W」のパラメータを調整する必要があります。

W=[0.45,0.5,100]

この面は以下の式を元に生成しています。

    y = w[0]*px + w[1]*py + w[2]

プログラム上の都合で変数名が上記のようになっていますが、実際にやりたいことは以下の通りです。

f:id:wantanBlog:20200119190004p:plain

表示範囲のxとyに対してパラメータの「w」を用いてzを表現しています。

パラメータ解析

入力値が一次元の場合、平均二乗誤差が式により表現が可能でした。
入力値が二次元の場合でも平均二乗誤差を式により導出してみます。

Z軸で表現すると最小二乗法の式は以下のように表すことができます。
ここから最適なw[x]の値を求めればよいのです。

f:id:wantanBlog:20200125022911p:plain

流れとしてはパラメータの数が変わっても同じで、w[x]に対する偏微分をおこいます。
それぞれの式がゼロになる場合を求めればそこがJの最小値となります。

f:id:wantanBlog:20200125025710p:plain

ここから先は実際に先ほどのJに関する式を偏微分し連立方程式を解くことになりますが相当しんどいのでそこは割愛します。

また式が煩雑になるので、一般的に使用されると言われる平均の表現を使用します。
「f(x)」という式の平均は「<f(x)>」で表現できます。

f:id:wantanBlog:20200125030450p:plain

連立方程式を解くとw[x]は以下のように表現されます。

f:id:wantanBlog:20200125031534p:plain

これはもう難解ですね・・・

var(x)」は統計用語で分散と呼ばれるものでそのまま数値のばらつきを表現しています。
数式的な意味としては、平均値からの差をとった偏差を二乗してから足し合わせ平均をとったものです。

cov(x,y)」は共分散と呼ばれるもので、xとyの数値の関係性を数値で表現したものと捉えてよさそうです。

それぞれ以下のように表現されます。

f:id:wantanBlog:20200125033331p:plain


非常に難解になってきましたので、ここからPythonの実装で済ませましょう。

・例

import numpy as npy
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

# データプロット
def show_model(ax, X, Y, Z):
    for i in range(len(X)):
        ax.plot([X[i],X[i]],[Y[i],Y[i]],[50,Z[i]],color='gray')
        
    ax.plot(X,Y,Z,'o',color='cornflowerblue',markeredgecolor='black',markersize=6, markeredgewidth=0.5)
    ax.view_init(elev=35, azim=-75)

# 面を生成
def show_plane(ax, w):
    px =npy.linspace(X_min, X_max, 5)
    py =npy.linspace(Y_min, Y_max, 5)
    px, py = npy.meshgrid(px, py)
    y = w[0]*px + w[1]*py + w[2]
    ax.plot_surface(px, py, y, rstride=1,cstride=1,alpha=0.3,color='blue',edgecolor='black')

# 二乗誤差
def mse_plane(X,Y,Z,w):
    y = w[0]*X + w[1]*Y +w[2]
    mse = npy.mean((y - Z)**2)
    return mse

# 共分散
def cov_func(a,b):
    return npy.mean(a*b) - npy.mean(a)*npy.mean(b)

# 解析解の導出関数
def fit_plane(x,y,z):
    cov_zx = cov_func(z, x)
    cov_zy = cov_func(z, y)
    cov_xy = cov_func(x, y)
    var_x = npy.var(x)
    var_y = npy.var(y)
    w0 = (cov_zy*cov_xy - var_y*cov_zx)/(cov_xy**2 - var_x*var_y)
    w1 = (cov_zx*cov_xy - var_x*cov_zy)/(cov_xy**2 - var_x*var_y)
    w2 = -w0*npy.mean(x) - w1*npy.mean(y) + npy.mean(z)
    return npy.array([w0, w1, w2])

# データをblog_data2.npzファイルから取り出す
sample_data = npy.load('blog_data2.npz')
X=sample_data['X']
Y=sample_data['Y']
Z=sample_data['Z']
X_min = min(X)
X_max = max(X)
Y_min = min(Y)
Y_max = max(Y)

plt.figure(figsize=(6,5))
ax = plt.subplot(1,1,1,projection='3d')
W = fit_plane(X,Y,Z)
print("w0={0:.1f},w1={1:.1f},w2={2:.1f}".format(W[0],W[1],W[2]))

show_plane(ax, W)
show_model(ax, X, Y, Z)
mse = mse_plane(X, Y, Z, W)

print("SD={0:.2f} cm".format(npy.sqrt(mse)))
plt.show()

・出力結果

w0=0.7,w1=0.3,w2=7.7
SD=18.35 cm

f:id:wantanBlog:20200127230007p:plain


show_model関数やshow_plane関数などは前のものをそのまま流用しています。
解析解の導出関数(fit_plane)は連立方程式を解くと求められるw[x]の式を表しています。

var関数(分散)はnumpyの標準ライブラリをそのまま使用できます。
cov関数(共分散)は関数を自前で用意すれば、それを流用できるので後は式に沿って関数を構築します。

偏微分による関数の導出は正直かなり難しいですが、そこまで求められれば実装自体は単純なもので方法で実装できると感じます。

この結果から何が分かるかと言うと、回帰モデルとのズレである標準偏差(SD)が入力パラメータが1つだったときと比較して小さくなっています。

1次元入力による勾配降下法SD=71.26
2次元入力による面モデルSD=18.35

相関のあるパラメータを増やすと入力値を増やした方が精度が上がることが分かります



今回はここまでにします。
次回はさらに入力パラメータを増やした場合でも対応できるようにしたいです。

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