波動と機械学習

人間の声を色相の変化を利用して3次元プロットした図に美しさを感じて、そのまま音声解析の道を進んでいます。自分なりに調べて実装できたものから更新していきます。アドバイス、アイデアなどあれば是非お願いします。

勾配降下法(実装編)

今回の記事では勾配降下法の実装を行います。
理論の勉強をしたい方は前回:勾配降下法(理論編)を参考にしてください。

また、今回用いるデータセット最小二乗法(実装編)で作成したものをそのまま使っていきます。

では、始めていきましょう。

勾配降下法の実装について

微分法の実装についての注意

勾配降下法を実装するうえで避けては通れないことは偏微分を実装することです。「偏微分が実装できる」ということは「微分が実装できる」ことにもなりますし、「極限を理解している」ということにもなります。

理論編でもそういった高校数学の知識は端折って説明していたので実装編でも簡単な説明のみで進めさせていただきます。

注意事項としては微分は極限をとります。我々人間はある値を0に近づけたり、無限に近づけたりすることで大体の値を得ます。
しかし、コンピュータは厳密に数値計算をしようとするため、桁が溢れて正しい数値が出なかったり、収束せずに解が得られなかったりします。
なので、「真の値に近似するように仕組みを作り、ある程度の妥協をする必要がある」ということを念頭において作業を進めてください。

1変数関数でプログラムを理解

1.)初期パラメータと関数の準備
仕組みを理解するためにまずは1変数関数を適当に用意。
初期パラメータも適当な値をxに代入します。

x=-100#適当な初期パラメータ
def g(x):
    y=2*(x-3)**2#任意の関数(極値持っていればどんな関数でも良いと思います。)
    return y

今回の関数はx=3で極値を持っています。

2.)微分法の実装
微分の定義は平均変化率の増加量を0に近づけることでした。
よってプログラム中のepsilonに対し、0に近い数値を与えることで近似します。

def DER(func,x):
    epsilon=0.001
    mse=func(x+epsilon)-func(x-epsilon)#yの増加量
    der=mse/(2*epsilon)#yの増加量/xの増加量
    return der

3.)微分した値を使ってパラメータの更新
微分した数値はそのままパラメータの更新に用いると発散して解が求まらない可能性があるため、学習率(プログラム中のalpha)をかけます。

for i in range(1000):#学習回数1000回
    alpha=0.1#学習率
    der=DER(g,x)
    x=x-der*alpha
    print(x)

f:id:araitbs007:20190228023845p:plain
xの値が3に近づいているのが分かると思います。
良い感じに出来上がりましたね。

最小二乗法を勾配降下法で解く

さて、本題です。
最低でも2変数関数の偏微分が必要です。しかし、偏微分といっても特別なことをするわけではありません。
ある一つの変数以外を固定してパラメータの更新をかけてやれば良いだけです。

4.)初期パラメータ・データセットの準備
実際に見てみます↓

#2.2 勾配降下法

import matplotlib.pyplot as plt
import numpy as np
import math
import random
%matplotlib inline

#初期パラメータ
a=1000
b=1000

#身長から算出する理想体重
def BMI(x):
    y=x*x*22
    return y

#データの作成(x:身長、y:体重)
x=np.array([])
y=np.array([])
for i in range(20):
    tall=np.random.normal(1.75 , 0.1)
    x=np.insert(x,len(x),tall,axis=0)
    norm=np.random.normal(0 , 2)
    weight=BMI(x[i])+norm
    y=np.insert(y,len(y),weight,axis=0)

print(x)
print(y)

※初期パラメータ以外は前回:最小二乗法(実装編)のコードから引用しただけです。

5.)偏微分の実装
今回はパラメータがaとbの2つあるので分かりやすいようあえて2つの関数に分けて定義しました。
本来であれば行列式を使い、関数も一つにまとめた方がスマートなのですが、分かりやすさを重視して実装していきたいと思います。
(「スマートではない」ということは3変数、4変数関数…など、「変数が増えるごとに実装が面倒なプログラムの書き方である」ということになります。)

1つ目はaの更新用に偏微分したものです。

def DER1(func,a,b,x,y):
    epsilon=0.0001
    mse=func(a+epsilon,b,x,y)-func(a-epsilon,b,x,y)
    der=mse/(2*epsilon)
    return der

2つ目はbの更新用に偏微分したものです。

def DER2(func,a,b,x,y):
    epsilon=0.0001
    mse=func(a,b+epsilon,x,y)-func(a,b-epsilon,x,y)
    der=mse/(2*epsilon)
    return der

3.)学習させる。
aの更新とbの更新は交互に行うことで勾配を降っていきます。。

for i in range(1000):
    alpha=0.5
    der=DER1(min2error,a,b,x,y)
    a=a-der*alpha#aの更新
    print(a)
    
    der=DER2(min2error,a,b,x,y)
    b=b-der*alpha#bの更新
    print(b)

学習して得られたパラメータ(a,b)が、前回:最小二乗法(実装編)で得られた値と近い数値が得られればそれで学習成功となります。

print("学習結果")
print(a)
print(b)

f:id:araitbs007:20190228030821p:plain
(今回私が実行したデータセットは、前回:最小二乗法(実装編)で実行したデータセットと異なるため、少し違う数値が現れています。)