LemonteaのUnity部屋

C#とかのお話です~

機械学習 教師有り 回帰 勾配法

皆さんこんにちは。レモンティーです。

今回は試しに教師有り機械学習の回帰問題に触れてみます。
今回は目覚ましを使わなかった時の
就寝時刻と起床時刻のデータを教師として与え、
それを元に就寝時刻から起床時刻を予測するモデルをつくります。

与えたデータは↓です。

# X就寝時刻 T起床時刻
X = np.array([22.5,23.1,21.4,24.9,23.6,22.1,25.7,22.3,20.1,22.7])
T = np.array([8.1,9.7,7.4,10.5,8.9,7.6,11.3,8.2,6.4,8.8])

f:id:sawalemontea:20180315172155p:plain

これからどう予測するかというと、直線のモデルを作ります。
{\displaystyle
y(x) = w_0x + w_1 と置いて、
}
{\displaystyle
y(x_n) が t_n にできるだけ近くなるw_0,w_1の組を探すわけですね。 
}
近さの指標としてyとtの二乗平均誤差
{\displaystyle
\ J = \frac{1}{N} \sum_{n=0}^{N-1}  (y_n-t_n)^2 \
}
つまり
{\displaystyle
\ J = \frac{1}{N} \sum_{n=0}^{N-1}  (w_0x_n+w_1-t_n)^2 \
}
を使います。
これをできるだけ小さくします。

小さくするには勾配を用います。
{\displaystyle
ある点(w_{0,0},w_{1,0})からスタートして勾配の逆方向に移動していくことで\\
Jを効率よく小さくすることができますね。\\
Jの(w_{0,t},w_{1,t})での勾配は偏微分によって以下のように求められます。
}
w0
{\displaystyle
\frac{\partial J}{\partial w_{0,t}} = \frac{1}{N} \sum_{n=0}^{N-1}  2(w_{0,t}x_n+w_{1,t}-t_n)*x_n\
}
{\displaystyle
\frac{\partial J}{\partial w_{0,t}} = \frac{2}{N} \sum_{n=0}^{N-1}  (w_{0,t}x_n+w_{1,t}-t_n)x_n\
}
w1
{\displaystyle
\frac{\partial J}{\partial w_{1,t}} = \frac{1}{N} \sum_{n=0}^{N-1}  2(w_{0,t}x_n+w_{1,t}-t_n)*1\
}
{\displaystyle
\frac{\partial J}{\partial w_{1,t}} = \frac{2}{N} \sum_{n=0}^{N-1}  (w_{0,t}x_n+w_{1,t}-t_n)\
}

よって以下のようにw0,w1を動かしてJを小さくしていきます
{\displaystyle
w_{0,t+1} = w_{0,t} - α *  \frac{2}{N} \sum_{n=0}^{N-1}  (y_n-t_n)x_n
}
{\displaystyle
w_{1,t+1} = w_{1,t} - α *  \frac{2}{N} \sum_{n=0}^{N-1}  (y_n-t_n)
}

αは学習率と言い、移動量の調節役です。

Jをz軸にとってグラフにすると↓のようになりますので、
この谷を駆け下りていく感じになります
f:id:sawalemontea:20180315172525p:plain
等高線で表すと↓なので、答えは
w0が0.3~1.5の間くらい、
w1が-25~3の間くらいになりそうですね。
f:id:sawalemontea:20180315172722p:plain

長いのでコードは最後に張ります。

結果は以下のようになりました
f:id:sawalemontea:20180315172735p:plain
たった86回というのが引っかかりますが
コンピューターはだいたい
w0=0.821,w1=-10.052
くらいがいいよと言っているみたいです。

ではこの直線
{\displaystyle
y(x) = 0.821x - 10.052
}
をTとXのグラフに重ねて書いてみて答え合わせをしましょう。

f:id:sawalemontea:20180315172850p:plain

まあまあいい感じのモデルができましたね。
さっきのMSEというのが平均二乗誤差で
MSE=0.128…でしたからルートをとって
0.357…が誤差になります。

つまり就寝時間を入力すればだいたい±20分くらいの精度で
起床時間を予測できるというわけですね。

今回はこれでおしまいです。
sawalemounity.hatenablog.com

以下はコードです

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

# X就寝時刻 T起床時刻
X = np.array([22.5,23.1,21.4,24.9,23.6,22.1,25.7,22.3,20.1,22.7])
T = np.array([8.1,9.7,7.4,10.5,8.9,7.6,11.3,8.2,6.4,8.8])


#平均誤差関数
def mse_line(x,t,w):
    y = w[0]*x + w[1]
    mse = np.mean((y - t)**2)
    return mse
        
#平均二乗誤差の勾配
def dmse_line(x,t,w):
    y = w[0]*x + w[1]
    d_w0 = 2*np.mean((y-t)*x)
    d_w1 = 2*np.mean(y-t)
    return d_w0,d_w1

#勾配法
def fit_line_num(x,t):
    w_init = [2.0,-10.0]
    alpha = 0.0001
    i_max = 100000
    eps = 0.1
    w_i = np.zeros([i_max,2])
    w_i[0,:] = w_init
    
    for i in range(1,i_max):
        dmse = dmse_line(x,t,w_i[i-1])
        w_i[i,0] = w_i[i-1,0]-alpha*dmse[0]
        w_i[i,1] = w_i[i-1,1]-alpha*dmse[1]
        if max(np.absolute(dmse)) < eps:
            break
    w0 = w_i[i,0]
    w1 = w_i[i,1]
    w_i =w_i[:i,:]
    return w0,w1,dmse,w_i


plt.figure(figsize=(4,4))
xn = 100
w0_range = [0,3]
w1_range = [-35,5]
x0 = np.linspace(w0_range[0],w0_range[1],xn)
x1 = np.linspace(w1_range[0],w1_range[1],xn)
xx0,xx1 = np.meshgrid(x0,x1)
J = np.zeros((len(x0),len(x1)))
for i0 in range(xn):
    for i1 in range(xn):
        J[i1,i0] = mse_line(X,T,(x0[i0],x1[i1]))
cont = plt.contour(xx0,xx1,J,30,colors='black',levels=(1,100,1000))
cont.clabel(fmt='%1.0f',fontsize=8)
plt.grid(True)

W0,W1,dMSE,W_history = fit_line_num(X,T)
#結果表示
print('繰り返し回数{0}'.format(W_history.shape[0]))
print('W=[{0:.6f},{1:.6f}]'.format(W0,W1))
print('dMSE=[{0:.6f}{1:.6f}]'.format(dMSE[0],dMSE[1]))
print('MSE={0:.6f}'.format(mse_line(X,T,[W0,W1])))
plt.plot(W_history[:,0],W_history[:,1],'.',color='gray',markersize=10)
plt.show()