深層学習／確率的勾配降下法(SGD)のシミュレーション

#1.はじめに
　深層学習を始めました。今回は、**確率的勾配降下法(SGD)を Jupyter Notebook でシミュレーションしてみます。**

　単純な勾配降下法は、全てのデータから傾きを計算して重みを更新するため、一度局所解に陥るとそこから抜け出し難くなり、計算するための時間も掛かります。　
　
　確率的勾配降下法(SGD)は、データの一部をランダムに抽出し傾きを計算して重みを更新するため、傾き計算が良い加減にブレて局所解を乗り越えてより最適な解に到達し易くなり、しかも計算する時間は短くて済みます。

　この、**良い加減なブレが、より最適な解に到達するための手段になる**ことがとても興味深く感じたので、今回は、確率的勾配降下法(SGD)をJupyter Notebook でシミュレーションしてみたいと思います。

#2.データ作成
　今回は、単純化するために重みは１個とします。適当に、x, y 座標を11点とって、6次元の多項式で近似します。

```python
import numpy as np
import matplotlib.pyplot as plt

# データ（多項式作成用）
x = np.array([-5.0,  -4.0,  -3.0,  -2.0,  -1.0,   0.0,   1.0,   2.0,   3.0,   4.0,    5.0])
y = np.array([ 5.0,   1.5,   2.0,   1.5,   0.0,  -3.0,  -1.0,   2.0,   3.0,   2.5,    5.0])

# 多項式作成（6次元）
p = np.poly1d(np.polyfit(x, y, 6))
print(p)

# データと多項式を表示
xp = np.linspace(-10, 10, 100)
plt.plot(x, y, '.', xp, p(xp), '')
plt.xlim(-7, 7)
plt.ylim(-5, 10)
plt.show()
```
![スクリーンショット 2020-01-09 22.20.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/d9e98e13-a01b-1a90-2148-b9336cea113e.png)
　得られた多項式を元に、x を-10〜10まで100分割して変化させた時のyを求めます。現実的には、観測値にノイズが乗っているはずなので、y には 0〜0.2の乱数を足しています。

```python
# 多項式から100点のデータを作成 （0〜0.2の乱数を足す）
x_add, y_add =[], []
for i in np.linspace(-10, 10, 100):
    x_add.append(i)
    y_add.append( p(i) + np.random.normal(0, 0.2)) 

# 作成したデータを表示
plt.scatter(x_add, y_add, alpha=0.5)
plt.xlim(-7, 7)
plt.ylim(-5, 10)
plt.show()
```
![スクリーンショット 2020-01-09 22.18.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/fee126db-75ab-8acb-07ab-67c45e7e17ca.png)
x = -4, 4 辺りに局所解、x = 0 辺りに最適解があるデータ(100点)が作成出来ました。


#3.確率的勾配降下法
　コードのメイン部分です。`train_test_split`を使って、100点のデータから10点をランダムにサンプリングします。

　その10点のデータだけを元に、6次元の多項式で近似し、`d_y = p.deriv()`で微分を求め、傾きを計算し重みを更新します。

　これを１画面づつ行い、matplotlib のアニメーションで動画化します。

```python
from sklearn.model_selection import train_test_split
from matplotlib import pylab
from matplotlib import animation, rc

# 設定
rc('animation', html='jshtml')
w = np.array([-2.])

# ランダムサンプリング関数（100点から10点をサンプリング）
def random_sampling():
    X_train, X_test, y_train, y_test = train_test_split(x_add, y_add, test_size=0.90)
    _x = X_train
    _y = y_train 
    return _x, _y

# １画面作成関数
def animate(frame, w, alpha):    
    _x, _y = random_sampling()
    p = np.poly1d(np.polyfit(_x, _y, 6))
    plt.plot(_x, _y, '.',
             xp, p(xp), '')
    d_y = p.deriv()
    
    plt.clf()
    plt.plot(xp, p(xp), '-', color='green')
    plt.plot(w, p(w), '.', color='red', markersize=20)
    plt.xlim(-7, 7)
    plt.ylim(-5, 10)  
    
    grad = d_y(w)
    w -= alpha * grad

# アニメーション作成関数
def gradient_descent(alpha, w):
    fig, ax = plt.subplots(111)
    if type(w) is list:
        w = np.array(w, detype=np.float32)
    anim = animation.FuncAnimation(fig, animate, fargs=(w, alpha), frames=100, interval=300) 
    
    return anim
```

#4.シミュレーション
　それでは、学習率 alpha = 0.3, 重みの初期値 x = 3.5 で、シミュレーションを実行してみます。

```python
# 学習率0.3、重みの初期値3.5で実行
gradient_descent(alpha=0.3, w=np.array([3.5]))  
```
![スクリーンショット 2020-01-09 22.59.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/caeebb64-1b93-0539-db8e-cbb2f9eb5ea5.png)
　コードを実行すると、こんな表示が現れますので、▶︎ボタンで再生してみて下さい。確率的なので、上手く行かない場合もありますが、何度か試すと良い加減なものが現れます。色々パラメーターをいじってみると面白いです。

　
　上手く行った例を下記に上げておきます（学習率 alpha = 0.3, 重みの初期値 X = 3.5, ループ再生）。良い加減な傾き計算によって、局所解X = 4 には留まらず、最適解 X = 0 にたどり着いています。
![ezgif-5-79b307de9cac.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/8c3119d9-91b7-53b0-cdf1-c7b72040f613.gif)