Python – 勾配法

勾配は、すべての変数に対する偏微分をベクトルにしたものです。勾配法は、勾配を使って関数が最小になるパラメタを探す方法のことです。偏微分はある点における、各パラメタに対する微分ですので、傾きです。傾きが分かるとパラメタをどう動かすとマイナスになるかが分かります。勾配はすべてのパラメタに対する傾きが入ってるので、これを使って全部のパラメタを徐々に関数の出力結果がマイナスになるように調整します。ただし、関数のグラフが凸凹している場合、ある部分においての凹の一番下についてしまった場合、出られなくなったりします。残念なことです。でも大体なんとかなるようです。ただしグラフの形状によっては、実際残念な結果に終わったり、最小値を発見するのにものすごく時間がかかったりするそうなのでケースバイケースで勾配法とは別の方法も試す必要があるそうです。

勾配によって、各パラメタの増減すべき方向が分かりますが、どの程度増減させるかは人間がいい感じの数字を設定する必要があります。この一回当たりにパラメタを増減させる数値は、学習率で決めます。偏微分結果に学習率を掛けたものが、増減数値になります。また、どこまでいけば本当の正解なのかは勾配法では分かりませんので、勾配法を何回繰り返すかも人間が決める必要があります。

勾配をPythonで書くと下記になります。

import numpy as np
 
def func(x):
    return x[0]**2 + x[1]**2
 
def gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)
    for i in range(x.size):
        tmp = x[i]
        x[i] = tmp + h
        y1 = f(x)
        x[i] = tmp - h
        y2 = f(x)
        grad[i] = (y1 - y2) / (2 * h)
        x[i] = tmp
    return grad
        
g = gradient(func, np.array([3.0, 4.0]))        
print(g)

勾配法をPythonで書くと下記になります。

def gradient_descent(f, x, lr=0.01, num=100):
    for i in range(num):
        x -= lr * gradient(f, x)
    return x

コードサンプル

import numpy as np

def func(x):
    return x[0]**2 + x[1]**2

def gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)
    for i in range(x.size):
        tmp = x[i]
        x[i] = tmp + h
        y1 = f(x)
        x[i] = tmp - h
        y2 = f(x)
        grad[i] = (y1 - y2) / (2 * h)
        x[i] = tmp
    return grad
        
def gradient_descent(f, x, lr=0.01, num=100):
    for i in range(num):
        x -= lr * gradient(f, x)
    return x

x2 = gradient_descent(func, np.array([3.0, 4.0]), 0.1, 100)
print(x2)

python – 損失関数

ディープラーニングで使う損失関数の、二乗和誤差と交差エントロピー誤差。

二乗和誤差

二乗和誤差は、差の二乗を全部足して2で割る。

def nijowagosa(y, t):
    return 0.5 * np.sum((y - t)**2)

y = np.array([0.2, 0.03, 0.8])
t = np.array([0, 0, 1])

print(nijowagosa(y, t))

交差エントロピー誤差


$$E = -\displaystyle \sum_{k}t_k \log y_k$$

logは底がeの自然対数を表すそうです。np.logでできます。ディープラーニングで使うときは、yが出力で、tが正解ラベルです。正解ラベルは正解を1とし、間違いを0とすると、正解ラベル以外のものは全部0になるので無視できて、-log yになります。間違いのやつ無視していいのかなと思いましたが、間違いのやつを高確率と判断した場合、ソフトマックス関数では正解の確立が低くなるので問題ないのかなと思いました。

def cross_error(y, t):
    delta = 1e-7
    return -np.sum(t * np.log(y + delta))

y = np.array([0.2, 0.03, 0.8])
t = np.array([0, 0, 1])
print(cross_error(y, t))

deltaは、np.log(0)が-infを出すことに対する対策です。超小さい数を加えています。

ディープラーニングのサンプルコード

メモ。ゼロから作るDeep Learningの本用のサンプルコードは、Githubで公開されてます。これは、Githubの公開リポジトリなのでブログに書いても大丈夫なやつなはずなのでメモ。

oreilly-japan/deep-learning-from-scratch

ソフトマックス関数

合計が1になるように、うまく数字を調整してくれる関数らしい。確率を確認するのに便利。


$$y_k = \frac{\exp(a_k)}{\displaystyle \sum_{i=1}^n \exp(a_i)}$$

aがn個あるときの、k番目のyを取得する。分母は全てのaの指数関数の和。分子はk番目のaの指数関数。

pythonで書くと下記になります。

a = np.array([0, 2.3, 3.5])
exp = np.exp(a)
sum_exp = np.sum(exp)
y = exp / sum_exp
print(y)

指数関数を使っているので数字がものすごく大きくなる可能性があり、それによってオーバフローで変な数値が返ってくる場合があるため、aの最大値をそれぞれのaの値から引きます。これで全部0かマイナスになります。

a = np.array([0, 2.3, 3.5])
c = np.max(a)
dexp = np.exp(a - c)
sum_exp = np.sum(exp)
y = exp / sum_exp
print(y)

ソフトマックス関数は、aのそれぞれの値の大きさの順番は変えません。なので、一番大きいaを取得したい場合は、ソフトマックス関数は不要です。

ディープラーニングの出力層で使う活性化関数は、回帰問題だと恒等関数、2クラス分類問題はシグモイド関数、多クラス分類問題はソフトマックス関数を使うのが一般的だそうです。シグモイドは0か1かを表すし、ソフトマックスは、複数の選択肢の確からしさを数値で表すからだと思います。回帰は数値を予測するので確率にする必要がないので恒等関数なんだと思います。

シグモイド関数

ディープラーニングでシグモイド関数というのを使いまっす。シグモイド関数は、何か入力されたら0~1の数値を返します。入力された数値が0未満だったら0を返して、そうでなければ1を返すような関数をステップ関数といいまして、これよりも緩やかに0~1の間を返すのでなんかいいらしいです。

シグモイド関数の式は、下記です。


$$h(x) = \frac{1}{1+\exp(-x))}$$

Pythonで書くと下記になります。

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

グラフにすると下記のようになります。

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()