JDLA認定プログラム：深層学習（Day1）

本記事は、Study-AI社様のJDLA認定プログラムの提出レポートを兼ねた記事となっております。

##講義要約
###sec1: 入力層〜中間層
 $入力x_i + 重みx_i = 総入力u$

###sec2: 活性化関数
 + ニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数。
 + 入力値の値によって、次の層への信号のON/OFFや強弱を定める働きを持つ。
   - ステップ関数：閾値を超えたら発火する関数で出力は1 or 0。パーセプトロンで利用。線形分離可能なものしか学習できない。
   $$ 
   f(x) = \begin{cases}
 1 (x \geqq 0) \\\
 0 (x < 0)
\end{cases}  
   $$
   - シグモイド関数：0~1の間を緩やかに変化する関数で、信号の強弱を伝えることで予想ニューラルネットワーク普及の契機となった。ただし大きな値では出力の変化が微細なため、勾配消失問題が起きやすい。
   $$
   f(u) = \frac{1}{1 + e^{-u}}
   $$
   - RELU関数：勾配消失問題の回避とスパース化に貢献。
  $$
 f(x) = \begin{cases}
 x (x > 0) \\\
 0 (x \leqq 0)
\end{cases}
  $$

###sec3-1: 出力層ー誤差関数
 + 事前データの訓練データとNNの出力値を比べ誤差を算出、間違い具合を数値化する。
 + 二乗和誤差（残差平方和）
 $$E_n(w) = \frac12 \sum_{j=1}^{l} (y_j - d_j)^2 = \frac12 \begin{Vmatrix} (y-d) \end{Vmatrix}^2 $$
 + 実際の学習にはクロスエントロピー誤差（分類）や平均二乗誤差（回帰）がよく用いられる。

###sec3-2: 出力層ー活性化関数
 + 出力層と中間層における活性化関数の役割の違い
    - 中間層：閾値の前後で信号の強弱を調整
    - 出力層：信号の大きさ（比率）はそのままに変換
 + 中間層用の活性化関数
    - ReLU関数
    - シグモイド関数
    - ステップ関数
 + 出力層用の活性化関数
    - 恒等写像（回帰、二乗誤差）
   $$f(u)=u$$
    - シグモイド関数（二値分類、交差エントロピー）
   $$
   f(u) = \frac{1}{1 + e^{-u}}
   $$
    - ソフトマックス関数（多クラス分類、交差エントロピー）
   $$ f(i, u) = \frac{e^{u_i}}{\sum_{K=1}^{K}e^{u_k}}$$

###sec4: 勾配降下法
 + 深層学習の目的：学習を通して誤差を最小にするネットワークを作成すること<br> = 誤差E(w)を最小化するパラメータwを発見すること
 + 勾配降下法を利用してパラメータを最適化
$$ w^{(t+1)} = w^{(t)} - \epsilon \nabla E $$
 + 今回の学習$w^{(t+1)}$を、前回の学習結果$w^{(t)}$から誤差関数で得られた誤差量$\epsilon \nabla E$を差し引くことで算出する。その際、適切な学習率$\epsilon$を見つけられると学習を有利に進められる。
$$ \nabla E = \frac{\partial E}{\partial w} = \begin{bmatrix} \frac{\partial E}{\partial w_1} \cdots \frac{\partial E}{\partial w_M}\end{bmatrix}
$$
 + 勾配降下法の学習率の決定、収束性向上のためのアルゴリズムについて複数の論文が公開されている
    - Momentum, AdaGrad, Adadelta, Adamなど
 + 確率的勾配降下法（SGD）
 $$ w^{(t+1)} = w^{(t)} - \epsilon \nabla E_n $$
    - ランダムに抽出したサンプルの誤差(勾配降下法は全サンプルの平均誤差)
    - データが冗長な場合の計算コストの軽減
    - 望まない局所最適解に収束するリスク低減
    - オンライン学習（モデルに都度データを与えてリアルタイムに行える学習）ができる
 + ミニバッチ勾配降下法
    - ランダムに分割したデータの集合（ミニバッチ）$D_t$に属するサンプルの平均誤差
 $$ w^{(t+1)} = w^{(t)} - \epsilon \nabla E_t $$
 $$ E_t = \frac{1}{N_t} \sum_{n \in D_t} E_n $$
 $$ N_t = \begin{vmatrix} D_t \end{vmatrix}$$
    - SGDのメリットを損なわず、計算機の計算資源を有効利用し同時並行でSGDによる学習を行うような方法（CPUを利用したスレッドの並列化やGPUを利用したSIMD並列化　※SIMD=Single Instruction Multi Data）
 + 誤差勾配の計算-数値微分
    - m番目の重みwに微小な値h分だけ増やした場合と、それからhを引いた場合を差し引いて平均をとることで、m番目の重みwが誤差Eを求めるのにどれだけ寄与したかを算出する
  $$ \nabla E = \frac{\partial E}{\partial w_m} \approx \frac{E(w_m+ h) - E(w_m - h)}{2h}$$
    - 各パラメータ$w_m$について計算するために計算量が膨大で負荷が大きい

###Sec5: 誤差逆伝播法
 + 算出された誤差を、出力層側から順に微分し、前の層へと伝播。最小限の計算で各パラメータでの微分値を解析的に計算する手法。
 + 計算結果（誤差）から微分を逆算することで、不要な再帰的計算を避けて微分を算出できる（連鎖律を利用することで計算量を極小化できる）。
 + $w^{(2)}$の値の調整を、実測値y - 正解値d で得られる誤差Eを使って行う場合
 $$ \frac{\partial E}{\partial y} \frac{\partial y}{\partial u^{(2)}} \frac{\partial u}{\partial w^{(2)}} = \frac{\partial E}{\partial w^{(2)}} $$

###A: 入力層の設計
 + 入力として取り得るデータ
    - 連続する実数
    - 確率
    - フラグ値 ([0, 0, 1]などone-hot-vector) 
 + 入力として取るべきでないデータ
    - 欠損値が多いデータ
    - 誤差の大きいデータ
    - 出力そのもの、出力を加工した情報 
    - 連続性の無いデータ（背番号など）
    - 無意味な数が割り当てられているデータ
         * 悪い例 Yes: 1, No: 0, どちらでもない: 0, 無回答: なし
         * 良い例 Yes : No : どちらでもない : 無回答 = [0, 0, 0, 1] (距離的な意味を持たない場合はone-hot-vectorが適切か)
 + 欠損値の扱い
    - ゼロで詰める
    - 欠損値を含む集合(列)を除外
    - 入力として採用しない(他の集合を含む行を除外)
 + データの結合
 + 数値の標準化・正規化
    - 標準化：0 - 1
    - 正規化：平均０、分散１

###B: データ拡張
 + 分類タスク（画像認識）に効果が高い
    - オフセット、ノイズ、ドロップアウト、拡大縮小、ジグソー、剪断、圧縮など
 + 密度推定のためのデータは水増しできない
 + データ拡張の結果、データセット内で混同するデータが発生しないよう注意 (６の回転画像と９の画像など)
 + 中間層へのノイズ注入で様々な抽象化レベルでのデータ拡張が可能（NNの出力層、中間層などそれぞれのレベルにおいてノイズを加えることで、それぞれのレベルでの特徴のバリエーションを増やすことが出来る）
 + データ拡張の効果と性能評価
    - データ拡張を行うとしばしば劇的に汎化性能が向上する。
    - ランダムなデータ拡張を行うときは学習データが毎度異なるため再現性には注意が必要。
 + データ拡張とモデルの捉え方
    - 一般的に適用可能なデータ拡張（ノイズ付加など）はモデルの一部として捉える（ドロップアウト層など）。
    - 特定の作業に対してのみ使用可能なデータ拡張（クロップなど）は入力データの事前加工として捉える（例：不良品検知の画像認識モデルに製品の一部だけが拡大された画像は入力されない）。

##確認テストと考察
###sec0.
* ディープラーニングは何をしようとしているか。<br>

```
明示的なプログラムの代わりに多数の中間層を持つニューラルネットワークを用いて、入力値から目的とする出力値に変換する数学モデルを構築すること。
```

* 次の中のどの値の最適化が最終目的か。<br>
入力値[X] / 2. 出力値[Y] / 3. 重み[W] / 4. バイアス[b] / 5. 総入力[u] / 6. 中間層入力[z] / 7. 学習率[p]<br>

```
3. 重み[W] 、 4. バイアス[b]
```

* 次のネットワークを紙に書け<br>
入力層：2ノード1層<br>
中間層：３ノード2層<br>
出力層：1ノード1層<br>

![写真 2020-11-30 12 41 48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/816981/0a193810-e09c-c3b7-1a5f-c2dca4a09664.png)

###Sec1.
* 図式に動物分類の実例を入れよ。

![スクリーンショット 2020-11-30 12.59.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/816981/0efc0d1b-d825-226a-bc08-92a6293fa494.png)

* 数式をPythonで書け。

```python
import numpy as np
u1 = np.dot(x, W1) + b1
```

* 1 - 1 ファイルから中間層の出力を定義しているソースを抜き出せ。
  
```python
# 2層の総入力
u2 = np.dot(z1, W2) + b2
# 2層の総出力    
z2 = functions.relu(u2)
```

###sec2.
* 線形と非線形の違いを図示、簡易に説明せよ。

```
線形：１次関数。加法性・斉次性を満たす。
非線形：２次以降ｎ次関数。加法性・斉次性を満たさない。
```

 ![スクリーンショット 2020-11-30 19.49.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/816981/3f7dd381-9bd0-de33-6602-f73369cb86f2.png)

* 配布されたソースコードより該当する箇所を抜き出せ。

```python
# 1層の総出力
z1 = functions.relu(u1)
# 2層の総出力
z2 = functions.relu(u2)
```

###Sec3-1.
* なぜ引き算でなく２乗するか述べよ。

```
正負両値があることで誤差の総和が正しく表現できない問題を回避するため。
```

* 二乗誤差の$\frac12$はどういう意味を持つか述べよ。

```
微分を求める計算の都合上出てくる２を打ち消すため。
```

###Sec3-2.
* ソフトマックス関数の１〜３の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。

```python
1: def softmax(x):
#ソフトマックス関数を定義する。
2: np.exp(x) 
#指数eひとつを割る。
3: np.sum(np.exp(x))
#指数eを全て足し合わせる。
```

* 交差エントロピーの１〜２の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。

```python
1: def cross_entropy_error(d, y):
#交差エントロピー関数を定義する。yは0と1が並んだもので正解が1。dはNNの実際の出力値。
2: -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7))
#d*log(y)を全て足し合わせたものを正負逆転させる。1e-7は対数関数が０の時に-∞になるのを回避する役割。
```

###Sec4.
* 対応するソースコードを探せ。

```python
network[key]  -= learning_rate * grad[key]
```

* オンライン学習とは何か。

```
モデルに都度データを与えてパラメータを更新し、リアルタイムに進めていく学習。（一方、バッチ学習では一度に全ての学習データを使ってパラメータ更新を行う。）
```

* この数式の意味を図に書いて説明せよ。
$$ w^{(t+1)} = w^{(t)} - \epsilon \nabla E_t $$

```
学習率 $\epsilon$ に応じて誤差 $E_t$ を調節し $w^{(t)}$ から $w^{(t+1)}$ へ学習を進める。(t+nはエポックの回数)
```
![スクリーンショット 2020-12-01 22.53.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/816981/c871bb60-d113-2bf4-711b-23c854dcc954.png)

###Sec.5
* 誤差逆伝播法では不要な再帰的処理を避けることが出来る。既に行った計算結果を保持しているソースコードを抽出せよ。

```python
delta2 = functions.d_mean_square
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
```

* 次の２つの数式に該当するソースコードを探せ。
$$ \frac{\partial E}{\partial y} \frac{\partial y}{\partial u}  $$

```python
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
```

$$ \frac{\partial E}{\partial y} \frac{\partial y}{\partial u} \frac{\partial u}{\partial w^{(2)}_{ji}}  $$

```python
grad['W1'] = np.dot(x.T, delta1)
```

## 演習結果と考察
###1_1_forward_propagation

```python:順伝播（単層・単ユニット）
# 重み
## 試してみよう_配列の初期化
W = np.zeros(2)
print_vec("np.zeros(2)", W)

W = np.ones(2)
print_vec("np.ones(2)", W)

W = np.random.rand(2)
print_vec("np.random.rand(2)", W)

W = np.random.randint(5, size=(2))
print_vec("np.random.randint(5, size=(2))", W)


# バイアス
## 試してみよう_数値の初期化
b = np.random.rand() # 0~1のランダム数値
print_vec("np.random.rand()", b)

b = np.random.rand() * 10 -5  # -5~5のランダム数値
print_vec("np.random.rand()", b)
```
**実行結果**
>*** np.zeros(2) ***
[0. 0.]
*** np.ones(2) ***
[1. 1.]
*** np.random.rand(2) ***
[0.58855967 0.56876571]
*** np.random.randint(5, size=(2)) ***
[2 1]
*** np.random.rand() ***
0.21843415119648857
*** np.random.rand() ***
-2.937606115879067

```python:順伝播（単層・複数ユニット）
# 重み
## 試してみよう_配列の初期化
W = np.zeros((4,3))
print_vec("np.zeros((4,3))", W)

W = np.ones((4,3))
print_vec("np.ones((4,3))", W)

W = np.random.rand(4,3)
print_vec("np.random.rand(4,3)", W)

W = np.random.randint(5, size=(4,3))
print_vec("np.random.randint(5, size=(4,3))", W)
```

**実行結果**
>*** np.zeros((4,3)) ***
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
*** np.ones((4,3)) ***
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
*** np.random.rand(4,3) ***
[[0.9424252  0.23872102 0.44954225]
 [0.25448821 0.93554578 0.87032127]
 [0.64246365 0.10390456 0.83096029]
 [0.03823406 0.31421025 0.9132785 ]]
*** np.random.randint(5, size=(4,3)) ***
[[2 2 3]
 [1 4 0]
 [3 4 1]
 [1 1 4]]

```python:順伝播（3層・複数ユニット）
# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")
    network = {}
    
    input_layer_size = 3
    hidden_layer_size_1=10
    hidden_layer_size_2=5
    output_layer_size = 4
    
    #試してみよう
    #_各パラメータのshapeを表示
    #_ネットワークの初期値ランダム生成
    network['W1'] = np.random.rand(input_layer_size, hidden_layer_size_1)
    network['W2'] = np.random.rand(hidden_layer_size_1,hidden_layer_size_2)
    network['W3'] = np.random.rand(hidden_layer_size_2,output_layer_size)

    network['b1'] =  np.random.rand(hidden_layer_size_1)
    network['b2'] =  np.random.rand(hidden_layer_size_2)
    network['b3'] =  np.random.rand(output_layer_size)

    print_vec("重み1", network['W1'] )
    print("shape: " + str(network['W1'].shape))
    print_vec("重み2", network['W2'] )
    print("shape: " + str(network['W2'].shape))
    print_vec("重み3", network['W3'] )
    print("shape: " + str(network['W3'].shape))
    print_vec("バイアス1", network['b1'] )
    print("shape: " + str(network['b1'].shape))
    print_vec("バイアス2", network['b2'] )
    print("shape: " + str(network['b2'].shape))
    print_vec("バイアス3", network['b3'] )
    print("shape: " + str(network['b3'].shape))

    return network
```

**実行結果**
>*** 重み1 ***
[[0.5357637  0.59076725 0.49447426 0.52449725 0.86981291 0.86746017
  0.60768308 0.28672878 0.25979569 0.53688677]
 [0.80576756 0.35918132 0.24304804 0.26059228 0.4984434  0.45860361
  0.80710464 0.66088368 0.2412577  0.82416434]
 [0.15043968 0.51981568 0.77635372 0.24526161 0.58546383 0.23752517
  0.65434452 0.92955912 0.11107778 0.45900792]]
shape: (3, 10)
*** 重み2 ***
[[7.52720944e-01 7.80795927e-01 7.22579708e-01 6.62980925e-01
  6.11234958e-01]
 [7.70753283e-01 7.11869569e-01 9.38488882e-01 7.61009894e-01
  1.32186479e-01]
 [4.51344201e-01 4.17268209e-01 5.64075402e-04 4.30388989e-01
  6.81991353e-01]
 [6.93441192e-01 8.50120779e-01 1.33960003e-01 3.49796776e-01
  3.38648606e-02]
 [6.68167471e-01 7.27010310e-01 2.29482841e-01 8.54155001e-02
  2.04435077e-01]
 [9.49437140e-01 4.15588899e-01 7.52022355e-01 6.59234166e-01
  8.84563176e-01]
 [2.42462958e-01 2.71124552e-01 4.77303482e-01 7.51102582e-01
  4.05807200e-01]
 [8.76281981e-01 6.76308277e-01 3.17305056e-01 7.54466384e-01
  8.78793557e-01]
 [5.97725102e-02 7.61833145e-01 2.28629349e-01 4.62355556e-01
  5.63681610e-01]
 [1.27306373e-01 4.90829172e-01 1.10414014e-01 7.15831650e-02
  4.78017149e-01]]
shape: (10, 5)
*** 重み3 ***
[[0.9777839  0.05068571 0.74203006 0.82485899]
 [0.04716596 0.30693941 0.36628234 0.38474185]
 [0.06169047 0.78737441 0.97263935 0.74887839]
 [0.07439204 0.06349749 0.63235994 0.41704748]
 [0.73738494 0.12146295 0.79050393 0.84846249]]
shape: (5, 4)
*** バイアス1 ***
[0.58900632 0.12812967 0.67544905 0.90675453 0.77817079 0.3102879
 0.35125951 0.32745768 0.45301344 0.99143296]
shape: (10,)
*** バイアス2 ***
[0.48654646 0.68649879 0.41683399 0.57640942 0.02899115]
shape: (5,)
*** バイアス3 ***
[0.97183403 0.01142156 0.1728953  0.74723359]
shape: (4,)

```python:多クラス分類
# 2-3-4ネットワーク

# ！試してみよう_ノードの構成を 3-5-6 に変更してみよう

# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")

    #試してみよう
    #_各パラメータのshapeを表示
    #_ネットワークの初期値ランダム生成

    network = {}
    
    input_layer_size = 3
    hidden_layer_size= 5
    output_layer_size = 6
    
    #試してみよう
    #_各パラメータのshapeを表示
    #_ネットワークの初期値ランダム生成
    network['W1'] = np.random.rand(input_layer_size, hidden_layer_size)
    network['W2'] = np.random.rand(hidden_layer_size,output_layer_size)
    
    network['b1'] =  np.random.rand(hidden_layer_size)
    network['b2'] =  np.random.rand(output_layer_size)
    

    print_vec("重み1", network['W1'] )
    print("shape: " + str(network['W1'].shape))
    print_vec("重み2", network['W2'] )
    print("shape: " + str(network['W2'].shape))
    print_vec("バイアス1", network['b1'] )
    print("shape: " + str(network['b1'].shape))
    print_vec("バイアス2", network['b2'] )
    print("shape: " + str(network['b2'].shape))

    return network
```

**実行結果**
>*** 重み1 ***
[[0.06025271 0.8235166  0.86387814 0.44735867 0.92060772]
 [0.21410535 0.1171699  0.32245159 0.00553575 0.15248127]
 [0.6265657  0.33050382 0.21993661 0.38449071 0.30058032]]
shape: (3, 5)
*** 重み2 ***
[[0.08238099 0.85665888 0.09275962 0.42785765 0.64266864 0.63311849]
 [0.88028174 0.47653607 0.66164049 0.61245071 0.17588199 0.31032141]
 [0.47845328 0.96299946 0.17232823 0.4168705  0.05671629 0.29852345]
 [0.35909897 0.2343279  0.35696851 0.66137309 0.83382177 0.75255879]
 [0.58141015 0.71894437 0.7244602  0.79162083 0.94270195 0.96094042]]
shape: (5, 6)
*** バイアス1 ***
[0.50029966 0.20003461 0.85046416 0.16212814 0.01543428]
shape: (5,)
*** バイアス2 ***
[0.59568209 0.70270781 0.61402068 0.55619762 0.19036038 0.27129295]
shape: (6,)

```python:回帰
# 2-3-2ネットワーク

# ！試してみよう_ノードの構成を 3-5-4 に変更してみよう

# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")

    input_layer_size = 3
    hidden_layer_size= 5
    output_layer_size = 4
    
    #試してみよう
    #_各パラメータのshapeを表示
    #_ネットワークの初期値ランダム生成
    network['W1'] = np.random.rand(input_layer_size, hidden_layer_size)
    network['W2'] = np.random.rand(hidden_layer_size,output_layer_size)

    network['b1'] =  np.random.rand(hidden_layer_size)
    network['b2'] =  np.random.rand(output_layer_size)
    
    print_vec("重み1", network['W1'] )
    print("shape: " + str(network['W1'].shape))
    print_vec("重み2", network['W2'] )
    print("shape: " + str(network['W2'].shape))
    print_vec("バイアス1", network['b1'] )
    print("shape: " + str(network['b1'].shape))
    print_vec("バイアス2", network['b2'] )
    print("shape: " + str(network['b2'].shape))

    return network
```

**実行結果**
>*** 重み1 ***
[[0.37114403 0.87711709 0.52973269 0.67407766 0.28939847]
 [0.96169996 0.53473814 0.29364414 0.9304494  0.23226097]
 [0.51450926 0.85784142 0.29695995 0.23514981 0.62828974]]
shape: (3, 5)
*** 重み2 ***
[[0.0887428  0.95306643 0.35048137 0.05433916]
 [0.57150537 0.68764841 0.77645601 0.70430318]
 [0.38348573 0.04947392 0.54511313 0.84006492]
 [0.56487534 0.35084259 0.69956263 0.0184263 ]
 [0.33777263 0.59916991 0.33024905 0.92228548]]
shape: (5, 4)
*** バイアス1 ***
[0.09956578 0.54905761 0.20480338 0.3239292  0.66073854]
shape: (5,)
*** バイアス2 ***
[0.03227863 0.94975435 0.28589746 0.01543701]
shape: (4,)

```python:2値分類
# 2-3-1ネットワーク

# ！試してみよう_ノードの構成を 5-10-20-1 に変更してみよう

# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")

    network = {}
    network['W1'] = np.array([
        [0.1, 0.3, 0.5,0.1, 0.3, 0.5,0.1, 0.3, 0.5, 0.1],
        [0.1, 0.3, 0.5,0.1, 0.3, 0.5,0.1, 0.3, 0.5, 0.1],
        [0.1, 0.3, 0.5,0.1, 0.3, 0.5,0.1, 0.3, 0.5, 0.1],
        [0.1, 0.3, 0.5,0.1, 0.3, 0.5,0.1, 0.3, 0.5, 0.1],
        [0.1, 0.3, 0.5,0.1, 0.3, 0.5,0.1, 0.3, 0.5, 0.1]
    ])
    network['W2'] = np.random.rand(10, 20)
    network['W3'] = np.random.rand(20, 1)

    network['b1'] = np.random.rand(10)
    network['b2'] =np.random.rand(20)
    network['b3'] =np.random.rand(1)

    return network
```

###1_3_stochastic_gradient_descent

```python:
# サンプルとする関数
#yの値を予想するAI

def f(x):
    y = 3 * x[0] + 2 * x[1]
    return y

# 初期設定
def init_network():
    #print("##### ネットワークの初期化 #####")
    network = {}
    nodesNum = 10
    network['W1'] = np.random.randn(2, nodesNum)
    network['W2'] = np.random.randn(nodesNum)
    network['b1'] = np.random.randn(nodesNum)
    network['b2'] = np.random.randn()

    #print_vec("重み1", network['W1'])
    #print_vec("重み2", network['W2'])
    #print_vec("バイアス2", network['b2'])

    return network

# 順伝播
def forward(network, x):
    #print("##### 順伝播開始 #####")
    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    u1 = np.dot(x, W1) + b1
    #z1 = functions.relu(u1)
    
    ## 試してみよう
    z1 = functions.sigmoid(u1)
    
    u2 = np.dot(z1, W2) + b2
    y = u2

    #print_vec("総入力1", u1)
    #print_vec("中間層出力1", z1)
    #print_vec("総入力2", u2)
    #print_vec("出力1", y)
    #print("出力合計: " + str(np.sum(y)))    
    
    return z1, y

# 誤差逆伝播
def backward(x, d, z1, y):
    #print("\n##### 誤差逆伝播開始 #####")    

    grad = {}
    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']

    # 出力層でのデルタ
    delta2 = functions.d_mean_squared_error(d, y)
    # b2の勾配
    grad['b2'] = np.sum(delta2, axis=0)
    # W2の勾配
    grad['W2'] = np.dot(z1.T, delta2)
    # 中間層でのデルタ
    #delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)

    ## 試してみよう
    delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)

    delta1 = delta1[np.newaxis, :]
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    x = x[np.newaxis, :]
    # W1の勾配
    grad['W1'] = np.dot(x.T, delta1)
    
    # print_vec("偏微分_重み1", grad["W1"])
    # print_vec("偏微分_重み2", grad["W2"])
    # print_vec("偏微分_バイアス1", grad["b1"])
    # print_vec("偏微分_バイアス2", grad["b2"])

    return grad

# サンプルデータを作成
data_sets_size = 100000
data_sets = [0 for i in range(data_sets_size)]

for i in range(data_sets_size):
    data_sets[i] = {}
    # ランダムな値を設定
    # data_sets[i]['x'] = np.random.rand(2)
    
    ## 試してみよう_入力値の設定
    data_sets[i]['x'] = np.random.rand(2) * 10 -5 # -5〜5のランダム数値
    
    # 目標出力を設定
    data_sets[i]['d'] = f(data_sets[i]['x'])
    
losses = []
# 学習率
learning_rate = 0.07

# 抽出数
epoch = 1000

# パラメータの初期化
network = init_network()
# データのランダム抽出
random_datasets = np.random.choice(data_sets, epoch)

# 勾配降下の繰り返し
for dataset in random_datasets:
    x, d = dataset['x'], dataset['d']
    z1, y = forward(network, x)
    grad = backward(x, d, z1, y)
    # パラメータに勾配適用
    for key in ('W1', 'W2', 'b1', 'b2'):
        network[key]  -= learning_rate * grad[key]

    # 誤差
    loss = functions.mean_squared_error(d, y)
    losses.append(loss)

print("##### 結果表示 #####")    
lists = range(epoch)


plt.plot(lists, losses, '.')
# グラフの表示
plt.show()
```
 * xの値の範囲を変えることで、データの散らばり具合が著しく大きくなることが確認できた。