ニューラルネットワークを作りたい！　Part2

# はじめに
　本記事はライブラリを一切使わず、ニューラルネットワークを組む方法について数学的な説明と共に解説する記事となっています。説明はC言語で書きますが、他言語を使う人でも参考になると思います。
　ニューラルネットワークをNNと略している部分があります。

第一回:ニューラルネットワークを理解したい！

https://qiita.com/TBjustice/items/e0e83107a317e31f27e7

第二回：ニューラルネットワークを作りたい！　Part1

https://qiita.com/TBjustice/items/8495222cb97d0c42002f


　第三回目のテーマは二回目に引き続き「ニューラルネットワークを作りたい！」です。

# パラメータを更新しよう
![NeuralNet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3039281/d7683e25-4b29-626c-7d28-e824be8f47ad.png)
　A-B間、C-D間は**全結合層**と呼ばれ、ニューラルネットワークにおけるパラメータが格納されていました。ここからはこのパラメータを更新する方法について議論していきます。
```c
// 変数名(前回の記事の物を再掲)
float A[2];
float B[3];
float C[3];
float D[2];
float E[2];
float answer[2];
float loss;
// パラメータの変数名(前回の記事の物を再掲)
float P_AB[3][2];
float Bias_AB[3];
float P_CD[2][3];
float Bias_CD[2];
```

　パラメータの調整をするには「あるパラメータ(p)を少しだけ増やすと、Lossは増えるか減るか」という値、所謂微分した値が必要という事を第一回目の記事で書きました。これを数学の記号で下のように書きます。
$$
\frac{∂Loss}{∂p}
$$

　これが正なら「pを増やせばLossが大きくなる・pを減らせばLossが小さくなる」事を意味し、これが負なら「pを増やせばLossが小さくなる・pを減らせばLossが大きくなる」事を意味します。また、この値が0に近い場合は「pはLossとあまり関係が無い」事を意味します。
　そこで、パラメータの更新は$p_{new}=p_{old}-η\times \frac{∂Loss}{∂p}$と言うように$\frac{∂Loss}{∂p}$を引く形で更新する事になります。なお、$η$は学習率と呼ばれます。

::: note
パラメータの調整は$p_{new}=p_{old}-η\times \frac{∂Loss}{∂p}$
$η$は学習率と呼ばれる。
:::

　さて、この値を求めるために微分（正確には偏微分）を行うのですが、これを愚直に計算しようと思うと物凄く難しいです。そこで逆伝播と言う物を使います。

# 偏微分を求める前準備「逆伝播」
　逆伝播という作業では$\frac{∂Loss}{∂B[i]}$や$\frac{∂Loss}{∂E[i]}$を求めます。つまり、「配列上のある値(例えばB[1])が少しだけ変化した時に、Lossはどれだけ変化するか」です。この値を保存する配列を用意しましょう。
```c
float dldA[2];
float dldB[3];
float dldC[3];
float dldD[2];
float dldE[2];
```

　複雑なことは忘れて、ひとまず答えから言ってしまうと以下の様に計算する事ができます。

```c
void calculateDLDE(){
    for(int i=0;i<2;i++){
        dldE[i]=E[i]-answer[i];
    }
}
void calculateDLDD(){
    for(int i=0;i<2;i++){
        float result = 0.0f;
		for (size_t j = 0; j < 2; ++j) {
            // E[i]とE[j]とdldE[j]を使った式であることに注意
            if(i == j){
    			result+=E[j]*(1 - E[i])*dldE[j];
            }
            else{
                result+=-E[j]*E[j]*dldE[j];
            }
		}
        dldD[i] = result;
    }
}
void calculateDLDC(){
    for(int i=0;i<3;i++){
        float result = 0f;
        for(int j=0;j<2;j++){
            result+= dldD[j] * P_CD[i][j];
        }
        dldC[i] = result;
    }
}
void calculateDLDB(){
    for(int i=0;i<3;i++){
        if(B[i] >= 0){
            dldB[i] = dldC[i];
        }
        else{
            dldB[i] = 0;
        }
    }
}
void calculateDLDA(){
    for(int i=0;i<2;i++){
        float result = 0f;
        for(int j=0;j<3;j++){
            result+= dldB[j] * P_AB[i][j];
        }
        dldA[i] = result;
    }
}
```

<details>
    <summary>
        算出
    </summary>

　まずは$dldE[0] = \frac{∂Loss}{∂E[0]}$を求めましょう。
　そもそもLossは以下のような式で表されました。
$$
Loss = \frac{1}{2} \times \sum_{i}(E_i-answer_i)^2
$$

　展開すると$Loss=\frac{1}{2} \times ((E_0-answer_0)^2+(E_1-answer_1)^2)$ですね。
　これをE[0]について偏微分すると以下のようになります。
$$
\frac{∂Loss}{∂E[0]}=E_0-answer_0
$$

　次に$dldD[0] = \frac{∂Loss}{∂D[0]}$を「$dldE[0]$と$dldE[1]$を利用して」求めます。どういうことかイメージを掴んで頂くために具体的な数字を当てはめて考えます。
　例えば$dldE[0]=5$とします。つまり$E[0]$を0.001増やすと$Loss$は約0.005増えるという事です。また$dldE[1]=-1$とします。
　ここで、$\frac{∂E[0]}{∂D[0]}$と$\frac{∂E[1]}{∂D[0]}$が求まったとします。例えば$1$と$2$とします。すると次のようなことが言えるのではないでしょうか。

「D[0]を0.001増やすと、E[0]は0.001増え、E[1]は0.002増える」
「E[0]が0.001増えたので、Lossは0.005増える。しかしE[1]が0.002増えたのでLossは-0.002される。結果、Lossは0.003の増加となる」
「よってdldD[0]=3である」

　一般に次の式が成り立つことが知られています。
$$
\frac{∂Loss}{∂D[i]}=\sum_{j}(\frac{∂Loss}{∂E[j]}\times \frac{∂E[j]}{∂D[i]})
$$

　先ほど「$\frac{∂Loss}{∂E[j]}$」の計算法はお伝えしましたので、後は$\frac{∂E[j]}{∂D[i]}$を求めればいいです。

$$
E_j = \frac{e^{D_j}}{\sum_{k}e^{D_k}}=\frac{e^{D_j}}{e^{D_0}+e^{D_1}}=\frac{e^{D_j-C}}{e^{D_0-C}+e^{D_1-C}}
$$

　これを頑張って微分すれば求まります。詳しくは割愛しますが、答えは下の様になります。
$$
\frac{∂E[j]}{∂D[i]}=E_j((i==j ? 1 : 0) - E_i)
$$

　全結合層(AB間やCD間)でも
$$
\frac{∂Loss}{∂C[i]}=\sum_{j}(\frac{∂Loss}{∂D[j]}\times \frac{∂D[j]}{∂C[i]})
$$

　を使う事で算出できます。こちらは簡単なので、さくっと計算しますと。
$\frac{∂D[0]}{∂C[0]}=\frac{∂(C[0]*P_{0,0}+C[1]*P_{0,1}+C[2]*P_{0,2})}{∂C[0]}=P_{0,0}$
$\frac{∂D[1]}{∂C[0]}=\frac{∂(C[0]*P_{1,0}+C[1]*P_{1,1}+C[2]*P_{1,2})}{∂C[0]}=P_{1,0}$
　です。

　B-C間は「B[i]<0の時はC[i]=0、それ以外でB[i]=C[i]」なので以下のように書けます。
B[i]<0ならばdldB[i]=0
B[i]>=0ならばdldB[i]=dldC[i]

</details>


# 偏微分を求める
　Lossをパラメータで偏微分するとどうなるか、これを計算するのは難しいのですが、先ほどの準備さえ終わっていれば実は簡単に求める事ができます。
　再び具体的な数字の話をしますが、P_AB[0][0]が0.001だけ大きくなったとしましょう。何所の値がどれだけ変化するでしょうか？

…
……
………

　正解はB[0]がA[0]×0.001だけ増えます。B[0]=$\sum_{k=0}^1(a_k\times P_AB[0][0])$ですからね。
　という事は、P_AB[0][0]が0.001だけ大きくなったら、Lossは$(A[0]×0.001)×dldB[0]$だけ大きくなります。偏微分の形で書くと
$$
\frac{∂Loss}{∂P_{0,0}}=A[0]\times dldB[0]
$$
　です。

　以上で求めたい物が全て求まりました。