Deep Learning~最小勾配法~

#はじめに

自分が通っている大学の工学部では、1年生時に多くの数学的処理に関して学びます。けれど、実際どのように役立っているのか不明なままそれらの処理を行っているのが実情だと感じています。

そして多くの学生は、これらの数学的処理がつながる先を考えずに問題を解ければいいと考えているかもしれません。けれど、学年が上がるにつれ1年生時に習った数学的処理が実世界を記述するのに大きな意味を持ち、それが分かったときに面白さが生じると思っています。今回はそんな面白さをを共有できたらと願いこの記事を執筆します。

##1.凡授業で扱う一部の内容

工学部の授業でよく扱う問題として、2変数関数の最小値を扱う問題が出ますね。以下に例を示します。



　　　　　　　　　　　　　　$f(x_0,x_1) = x_0^2 + x_1^2$の最小値を求めよ。
　　
　　　　　　　　　　
学部1年生時代、この問題を解くのに$f(x,y)$を$x,y$で偏微分して、さらに二階の偏微分をして、判別式から最小値の吟味を行いますが、実際、この最小値を求める作業にどのような意味をもつのかは習わないと思います。

今回は、この2変数関数の最小値を求める行為が、ニューラルネットワーク構築にどのような場面で必要になるのかとともに解説できたらなと思います。

##2.ニューラルネットワークにおける最小値を求める意味とは

　　　　　　　　　　 "損失関数の最小値を求めることです"

もう少し解説したいと思います。

そもそも、ニューラルネットワークでの学習とは、重みとバイアスを訓練データに対応することができるようにする行為です。

そして、この学習という行為の中で大事な関数が損失関数というものです。教師データ（正解）に対しての訓練データは誤差を小さくすることであり、この誤差を最小とするのに損失関数が必要となってきます。

##3.勾配降下法

上記で記したように、損失関数の最小値を求める方法として勾配降下法があります。では、具体的に勾配降下法について見ていきましょう。

ここで、勾配降下法の式を以下に示します。

```math
x = x - μ\frac{∂f}{∂x}\\
y = y - μ\frac{∂f}{∂y}\\

```
と表すことができます（μは学習率）。ちなみに、この学習率とは上の式をみればわかるように1回の学習で、どれだけ重みやバイアスを更新するのかの値です。そして、この行為を関数f（損失関数）において、損失関数の最小値を求めていくことが大事になります。



```math
f(x_0,x_1) = x_0^2 + x_1^2
```
の最小値を実際に求めてみましょう。

```php
import numpy as np
import sys
def numercial_gradient(f, x):
 h = 1e-4   
 grad = np.zero_like(x)
 
 for idx in range(x.size):
         tmp_val = x[idx]
         x[idx] = tmp_val + h
         fxh1 = f(x)

         x[idx] = tmp_val - h
         fxh2 = f(x)

         grad[idx] = (fxh1 - fxh2)/(2*h)
         x[idx] = tmp_val
  return grad
#勾配を定義
def gradient_descent(f, init_x, lr=0.01, step_num=100):
##init_xは初期値、学習率lrを適切な値に設定、step_numは繰り返し数を表す
 x = init_x

 for i in range(step_num):
     grad = numerical_gradient(f, x) 
     x -= lr*grad #勾配降下法の式
 return x 
```
これで、勾配法の式を表すことはできました。次に、ここで定義した勾配法の式を用いて$f(x_0,x_1) = x_0^2 + x_1^2$の最小値を求めていきます。

```php
def function_2(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-2.0, 3.0])
gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)
```
これを実行すると
![スクリーンショット 2021-06-21 211934.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1424930/8438bca0-a1e1-d16c-c7bd-1be3c425a313.png)

このような結果になりました。これは、($x_0,x_1$) = ($0,0$)を示していますね。

##4.勾配法による更新のプロセスを追う

この勾配法で追った過程を視覚的に捉えてみましょう

```php

import sys
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm


def func2(X, Y):
    return X ** 2 + Y ** 2


def main(args):
    x_0 = np.arange(-5, 5, 0.25)
    x_1 = np.arange(-5, 5, 0.25)

    X, Y = np.meshgrid(x, y)
    Z = func2(X, Y)

    fig = plt.figure()
    ax = Axes3D(fig)

    ax.set_xlabel("x")
    ax.set_ylabel("x")
    ax.set_zlabel("f(x, y)")

    contour = ax.contourf(X, Y, Z, levels=20, cmap=cm.coolwarm)
    fig.colorbar(contour, shrink=0.5, aspect=10)
    plt.show()


if __name__ == "__main__":
    main(sys.argv)
```
今回の関数を等高線つきで見ると、以下のようになる。
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1424930/84da4eab-fbe3-77b6-f8bb-99bf3098c478.png)

たしかに$f(x,y)=(0,0)$で最小値をとっているようにみえますが、3次元で描かれたグラフではどこが最小値か見えにくいですね。次では、2次元に落とし込んで考えてみます。

コードは割愛します（長く冗長になるため）が、結果だけ示します。

![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1424930/a8383651-5a40-d397-057d-0fe803dc7f1a.png)

これを見る限り$f(x_0,x_1)=(0,0)$で最小値をとっていることが分かりますね！

##5.まとめ

今回は、実際に大学で扱われる数学の問題が実際にディープラーニングという場に応用されている例を見ました。今回は基本的な関数で最小値を求めましたが、実際は損失関数を使います（実験レポートを作成する際によく使う二乗和誤差もその一部です。）

大学で習う数学が解くで終わるのではなく、応用できているんだという面白さが共有できていたら幸いです。