Deep Learning に関するまとめノート -4.3 勾配法-

# 説明の概要
　ここでは私がDeep Learningの勉強をしているときに本を読むだけでは分からなかったことを自分なりに解釈し、後で見返したときにスムーズに思い出せることを目的としています。
コードの内容もなるべく丁寧に説明してるつもりなので参考になれば幸いです。
# 4.3 勾配法　編
前の記事:[Deep Learning に関するまとめノート -4.2 損失関数-](https://qiita.com/Meteora/items/1073a8914d198980f5e7)
でも説明したとおり、ニューラルネットワークの学習では最適なパラメータを探索して、損失関数の値を小さくしていくのが目的である。
　では具体的にどのようにパラメータを更新していくのか。方法として今回説明する勾配法を使うのである。
##　勾配法
　最初に簡単に勾配法で何を行っているのかの流れを書いておく。

　　　1. 初期地点*x*を指定
　　　2. 地点での関数の傾きを求める
　　　3. 傾きをもとに地点*x*を更新
　　　4. 傾きが0となる*x*が見つかるまで2-3を繰り返し。

勾配法では目的が最小値を探すことか、最大値を探すことかによって呼び名が異なり、
　　　前者を**勾配降下法**(gradient decent method)、
　　　後者を**勾配上昇法**(gradient ascent method)
と呼ぶ。今回はニューラルネットワークでよく登場する<font color="Red">**勾配降下法**</font>を例として説明していく。

---
### 数式

　勾配法の式は以下の式　

```math
x = x -n\frac{\partial f}{\partial x}

``` 
で示される。数式を見ると分かるとおり、関数f(x)をxについて微分して傾きを求めている。これにnを掛け、現在の地点から引くことで地点を更新していく。
関数f(x)の傾きが0になると微分の値は0になりパラメータは更新されなくなる。

　nは学習率(learning rate)と呼ばれるものであり、毎回の学習でどれくらいパラメータを更新するかを決める量である。値は大きいほど毎回のパラメータの移動距離が長くなり、小さいと短くなる。
後に説明するが<font color='Red'>**「学習率は適切な値を使用する必要がある」**</font>ということを注意してほしい。

---
### 勾配法の例 
今回の例では、関数を

```math
f(x_1,x_2) = x_0^2 + x_1^2

``` 
として、f(x1,x2)の値が最小になるx1,x2を勾配法によって探索してみる。




```python
#モジュールのインポート
import numpy as np
#微分を行う関数を定義
def numerical_gradient(function,x):
    h =  1e-4
    #xと同じ形状で要素がすべて0の配列を作成
    #微分された値を後に代入する
    grad = np.zeros_like(x)
    
    for idx in range(x.size):
        tmp_val = x[idx]
        #f(x+h)
        x[idx] = tmp_val + h
        fxh1 = function(x)
        #f(x-h)
        x[idx] = tmp_val -h
        fxh2 = function(x)
        
        #微分して値をgradに代入
        grad[idx] = (fxh1 - fxh2)/(2*h)
        #xの値をもとに戻す
        x[idx] = tmp_val
    # すべてのｘについて微分ができたらgradを返す
    return grad
    
#勾配降下法の関数を定義(今回のメイン）############################
def gradient_descent(function,init_x,lr=0.01,step_num=100):
#lrは学習率、step_numは回数。ここではDefaultでそれぞれ0.01、100
    #xは現在の地点（配列）
    x = init_x
    #step_num回地点の更新を行う。
    for i in range(step_num):
       
        grad = numerical_gradient(function,x)
        
        #勾配法の数式
        x = x - lr * grad
    #step_num回更新したら地点を出力   
    return x
############################################################
#テスト用の関数を作成
def testfunction(x):
    return x[0]**2 + x[1]**2
#テスト用のxを作成
testx = np.array([3,2])

#勾配法を実行、xの初期地点(init_x)、学習率(lr)、学習回数(step_num)を設定
gradient_descent(testfunction,init_x=testx,lr=0.1,step_num=100)

```
---
出力結果は
`array([-6.35809854e-07, -3.81434987e-07])`
となった。eが入っているので分かりづらいがつまりは、

```math
x_1 = -6.358 ×10^{-7} = - 0.0000006358 \\
 
x_2 = -3.814 ×10^{-7} = - 0.0000003814

``` 
である。これは、(x1,x2) = (0,0)に近い値となり、勾配法によってほぼ正しい結果を得ることが出来たと言える。

---
#### 学習率を適切な値にする理由
 学習率は大きすぎても、小さすぎてもいけない。この理由を上で書いたコードで確かめてみる。

- 学習率が大きすぎる場合
    コードのlrを0.1から1にしてみる。実行結果は以下のとおりである
    `array([-2499150084997, -1499450054998])`
これは(x1,x2)=(0,0)からほど遠い。
学習率が大きすぎる場合値が発散してしまう場合があるのがこの理由だ。
    
- 学習率が小さすぎる場合
    コードのlrを0.1から0.000001にしてみる。実行結果は以下のとおりである
    `array([2.97441101, 1.98460701])`
これも(x1,x2)=(0,0)から値が遠い。
理由は一回の学習で値がほとんど更新されず、step_num回では十分な学習が行われなかったからである。

## まとめ

- 勾配法は関数の値を最大もしくは最小にするために変数を更新していく方法である。
- 勾配法には勾配降下法(Gradient Decent Method)と勾配上昇法(Gradient Ascent Method)がある。
- 学習率は適切な値を指定する必要がある。
- 
##参考書  

[ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装 (日本語)](https://www.amazon.co.jp/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8BDeep-Learning-%E2%80%95Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AE%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%A3%85-%E6%96%8E%E8%97%A4-%E5%BA%B7%E6%AF%85/dp/4873117585/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=W6DVSLVW0BUS&dchild=1&keywords=%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8Bdeep+learning&qid=1599743190&sprefix=%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%2Caps%2C285&sr=8-1)]





### 実行環境
>OS: Windows 10/Ubuntu 20.04 LTS
>Jupyter Notebook
>Python Version: Python 3.8