ほぼPython

Not技術ブログBut勉強ブログ 内容には誤りがあることが多いです

最急降下法(勾配法)で直線モデルを求める

前回のブログで、勾配の話をしました。

勾配を逆にたどっていけば、(局所的な)最小値にたどり着ける、みたいな話です。

今回は、実際にその勾配を利用して、与えられたデータの組から当てはまりのいい直線を求めてみようと思います。
エクセルで「近似曲線の追加」をポチッとすればすぐに出てくるあれです。あんな感じのグラフを1から計算して描いていきます。

コード

まず、モジュールのインポートをします。

import numpy as np
import matplotlib.pyplot as plt

そうしたらデータの用意をします。今回は手動で適当に作りました。

#データの用意
x_data=np.array([i for i in range(0,10)])
y_data=np.array([5,1,6,8,10,12,17,16,20,25])

ちなみに手動でこのデータを作っている時、僕は何となく y = 2x + 5 くらいをイメージしていました。

次に、今回使う関数を作っておきます。

#偏微分
def dmse_calc(w,x=x_data,t=y_data):
    y=w[0]*x + w[1] #直線モデル
    dmse_w0 = np.mean((y-t)*x)*2 #平均二乗誤差をw0で偏微分(勾配ベクトルの成分0)
    dmse_w1 = np.mean(y-t)*2 #平均二乗誤差をw1で偏微分(勾配ベクトルの成分1)
    return dmse_w0,dmse_w1

#勾配法
def slope_calc():
    alpha = 0.001 #学習率
    w_init = [1,1] #初期値
    i_max = 10000 #繰り返し回数
    eps = 0.01 #ストップする値
    w_i = np.zeros((i_max,2)) #i_max行2列の行列を準備
    w_i[0] = w_init #0番目には初期値を入れておく
    for i in range(1,i_max):
        dmse = dmse_calc(w_i[i-1]) #各w_iの値での偏微分の値
        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
    return w_i[i],i,dmse

#誤差計算
def mse_calc(w,t=y_data,x=x_data):
    y = x*w[0] + w[1]
    mse = np.mean((y-t)**2)
    return mse

そうしたらこれらの関数を呼び出してグラフを描いて終わりです。

#勾配法呼び出し
w,count,dMSE = slope_calc()

#誤差計算呼び出し
MSE = mse_calc(w)

#直線グラフの準備
x_ziku = np.linspace(0,10,10)
y_ziku = w[0]*x_ziku + w[1]

#散布図と直線グラフ描画
plt.figure(figsize=(10,5))
plt.plot(x_data,y_data,marker='o',linestyle='None') #元データのプロット(散布図)
plt.plot(x_ziku,y_ziku,color='red') #計算した直線を表示
plt.text(3,20, "y="+str(np.around(w[0],3))+"x+"+str(np.around(w[1],3))) #直線の式を表示
plt.show()

print(w[0],w[1],count,dMSE,MSE) #傾き、切片、繰り返し回数、勾配ベクトル、平均二乗誤差を出力

その結果、グラフは、このようになります。
f:id:short_4010:20180228193752p:plain

エクセルで近似曲線を出した結果はy = 2.3758x + 1.3091 となったので、そこそこいい感じに求められていることがわかります。