ゼロから作る DeepLearning(Go) Part.2 ~ パーセプトロン ~

# ゼロから作る DeepLearning(Go) Part.2 ~ パーセプトロン ~

## はじめに

こんにちは。みです。

これは、「ゼロから作る DeepLerning ①」を Python ではなく、GO を使用してまとめてみた内容です。
参考：[ゼロから作る DeepLerning ①](https://www.oreilly.co.jp/books/9784873117584/)

良ければご覧ください。

＊＊＊＊↓ 前回はこちら ↓＊＊＊＊  
前回：[ゼロから作る DeepLearning(Go) Part.1](https://qiita.com/nena_i_mmao/items/64d673c66ffdafdd2c25)  

＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊

## 目次

1. Python 入門（GO での代用）
2. **パーセプトロン**　 ← 今回はこちら ★
3. ニューラルネットワーク
4. ニューラルネットワークの学習
5. 誤差逆伝播法
6. 学習に関するテクニック
7. 畳み込みニューラルネットワーク
8. ディープラーニング

## パーセプトロン

### 2.1 パーセプトロンとは

「人口ニューロン」、「単純パーセプトロン」と呼ばれるもの。
複数の**信号**を入力として受け取り、一つの信号を出力する。
**信号**とは、電流や川の持つ、「**流れ**」のイメージ。
電流が同船を流れ、電子を策に送り出すように、パーセプトロンの信号も流れを作り、情報を先へと伝達していく。

##### 二つの信号を受け取るパーセプトロンの例

![perceptron.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3891234/feabd641-4a9d-93d7-4954-5278cca1c620.png)

$x_1,x_2$は入力信号。
$w_1,w_2$は重み。(w は weight の頭文字。)
図の〇は、「**ニューロン**」や「**ノード**」と呼ばれます。
入力信号は、ニューロンに送られる際その固有の重みが乗算される。（$w_1x_1,w_2x_2$)
ニューロンでは、送られてきた信号の総和が計算される。
→ その結果ある限界値を超えた場合にのみ、１を出力する。
この限界値を**閾値**(**いきち**)と呼び、$\theta$で表す。
以上のことを数式で表すと以下のようになる。

##### 二つの信号を受け取るパーセプトロンの数式

$$
y=
\begin{cases}
0\quad(w_1x_1 + w_2x_2 \leqq\theta) \\
1\quad(w_1x_1 + w_2x_2 >\theta)
\end{cases}
$$

パーセプトロンは、複数ある信号のそれぞれに重みを持つ。
重みは、各信号の重要性をコントロールする要素である。
$w_1 > w_2 $の時、$w_1$の信号の重要性は高くなる。

### 2.2 単純な論理回路

#### 2.2.1 AND ゲート

AND ゲートとは、2 つの入力が 1 のときのみ 1 を出力する。

##### AND ゲートの真理値表

| $x_1$ | $x_2$ | y   |
| ----- | ----- | --- |
| 0     | 0     | 0   |
| 0     | 1     | 0   |
| 1     | 0     | 0   |
| 1     | 1     | 1   |

#### 2.2.2 NAND ゲートと OR ゲート

NAND ゲートとは、NOT AND を意味する。
AND ゲートを逆にしたもの。

##### NAND ゲートの真理値表

| $x_1$ | $x_2$ | y   |
| ----- | ----- | --- |
| 0     | 0     | 1   |
| 0     | 1     | 1   |
| 1     | 0     | 1   |
| 1     | 1     | 0   |

OR ゲートは、入力信号が少なくとも一つでも 1 があれば、1 を出力する。

##### OR ゲートの真理値表

| $x_1$ | $x_2$ | y   |
| ----- | ----- | --- |
| 0     | 0     | 0   |
| 0     | 1     | 1   |
| 1     | 0     | 1   |
| 1     | 1     | 1   |

### 2.3 パーセプトロンの実装

#### 2.3.1 簡単な実装

<details><summary>ANDゲートの実装</summary>

```go:perceptron.go
func And(x1 float64, x2 float64) {
	fmt.Printf("x1 = %g,x2 = %g\n", x1, x2)
	w1, w2, theta := 0.5, 0.5, 0.7
	tmp := x1*w1 + x2*w2
	fmt.Printf("result:")
	if tmp <= theta {
		fmt.Println(0)
	} else {
		fmt.Println(1)
	}
}
```

```go:main.go
func main(){
    And(0, 0) //0を出力
    And(1, 0) //0を出力
    And(0, 1) //0を出力
    And(1, 1) //１を出力
}
```

</details>

<details><summary>結果</summary>

```shell-session
$ go run main.go
    x1 = 0,x2 = 0
    result:0
    x1 = 1,x2 = 0
    result:0
    x1 = 0,x2 = 1
    result:0
    x1 = 1,x2 = 1
    result:1
```

</details>

#### 2.3.2 重みとバイアス導入

AND ゲートは単純なので、実装が簡単でしたが、別の実装方法に変更する。
$\theta$→-**b** (**バイアス**)に変更する。

##### 変更後パーセプトロンの数式

$$
y=
\begin{cases}
0\quad(b + w_1x_1 + w_2x_2 \leqq0) \\
1\quad(b + w_1x_1 + w_2x_2 >0)
\end{cases}
$$

#### 2.3.3 重みとバイアスによる実装

$\theta$を b に変更し、gonum を使用して AND,NAND,OR ゲートを実装する。

<details><summary>ANDゲートの実装</summary>

```go:perceptron.go
func (p *perceptron) And(x1 float64, x2 float64) float64 {
	x := mat.NewDense(1, 2, []float64{x1, x2})
	w := mat.NewDense(1, 2, []float64{0.5, 0.5})
	b := -0.7
	tmp := mat.Sum(p.calc.MulElem(x, w)) + b
	if tmp <= 0 {
		return 0
	} else {
		return 1
	}
}
```

```go:main.go
	fmt.Println("AND")
	fmt.Println(m.p.And(0, 0)) //0を出力
	fmt.Println(m.p.And(0, 1)) //0を出力
	fmt.Println(m.p.And(1, 0)) //0を出力
	fmt.Println(m.p.And(1, 1)) //1を出力
```

</details>
<details><summary>結果</summary>

```shell-session
$ go run main.go
    AND
    0
    0
    0
    1
```

</details>

<details><summary>NANDゲートの実装</summary>

```go:perceptron.go
func (p *perceptron) NotAnd(x1 float64, x2 float64) float64 {
	x := mat.NewDense(1, 2, []float64{x1, x2})
	w := mat.NewDense(1, 2, []float64{-0.5, -0.5})
	b := 0.7
	tmp := mat.Sum(p.calc.MulElem(x, w)) + b
	if tmp <= 0 {
		return 0
	} else {
		return 1
	}
}
```

```go:main.go
	fmt.Println("NAND")
	fmt.Println(m.p.NotAnd(0, 0)) //1を出力
	fmt.Println(m.p.NotAnd(0, 1)) //1を出力
	fmt.Println(m.p.NotAnd(1, 0)) //1を出力
	fmt.Println(m.p.NotAnd(1, 1)) //0を出力
```

</details>
<details><summary>結果</summary>

```shell-session
$ go run main.go
    NAND
    1
    0
    0
    0
```

</details>

<details><summary>ORゲートの実装</summary>

```go:perceptron.go
func (p *perceptron) Or(x1 float64, x2 float64) float64 {
	x := mat.NewDense(1, 2, []float64{x1, x2})
	w := mat.NewDense(1, 2, []float64{0.5, 0.5})
	b := -0.2
	tmp := mat.Sum(p.calc.DivElem(x, w)) + b
	if tmp <= 0 {
		return 0
	} else {
		return 1
	}
}
```

```go:main.go
	fmt.Println("OR")
	fmt.Println(m.p.Or(0, 0)) //0を出力
	fmt.Println(m.p.Or(0, 1)) //1を出力
	fmt.Println(m.p.Or(1, 0)) //1を出力
	fmt.Println(m.p.Or(1, 1)) //1を出力
```

</details>
<details><summary>結果</summary>

```shell-session
$ go run main.go
    OR
    0
    1
    1
    1
```

</details>

### 2.4 パーセプトロンの限界

パーセプトロンを用いれば、三つの論理回路を実装することができた。

#### 2.4.1 XOR ゲート

XOR ゲート「**排他的論理和**」と呼ばれる論理回路。
$x_1$と$x_2$のどちらかが 1 の時だけ、1 が出力されます。
（排他的とは、自分以外は拒否することを意味する。）
これは、これまでのパーセプトロンでは、実現できない。

##### XOR ゲートの真理値表

| $x_1$ | $x_2$ | y   |
| ----- | ----- | --- |
| 0     | 0     | 0   |
| 0     | 1     | 1   |
| 1     | 0     | 1   |
| 1     | 1     | 0   |

AND、OR ができて、XOR が実現できない理由を視角的に考えてみます。

##### OR ゲートの視角的挙動

重み・パラメーター(b,$w_1$,$w_2$)=(-0.5,1.0,1.0)
式：

$$
y=
\begin{cases}
0\quad(-0.5 + x_1 + x_2 \leqq0) \\
1\quad(-0.5 + x_1 + x_2 >0)
\end{cases}
$$

この式は、$ -0.5 + x_1 + x_2 = 0$の直線で分断された二つの領域を作成する。
図に表すと以下のようになる。
※図は大雑把なイメージです。

![orgate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3891234/314fe7d9-ff43-26c9-5f29-f5e1b6fe5ff6.png)


$(x_1,x_2) = (0,0)$の時 0(〇)
$(x_1,x_2) = (0,1),(1,0),(1,1)$の時 1(△)
すべて直線によって正しく分けることができる。

#### 2.4.2 線形と非線形

XOR ゲートは、先の OR ゲートのように 1 本の直線では表すことができません。
※直線という制約をなくせば表すことができる。
図に表すと以下のようになる。
※図は大雑把なイメージです。
![xorgate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3891234/30a5a80a-e57f-094d-13aa-660299f559c9.png)



パーセプトロンの限界は、1 本の直線で分けた領域だけしか表現できない点。
XOR ゲートの図のようなく曲線はパーセプトロンでは表現できない。
曲線による領域を**非線形**、直線による領域を**線形**と呼ぶ。

### 2.5 多層パーセプトロン

パーセプトロンは、**層を重ねる**ことで非線形を表現できるようになる！

#### 2.5.1 既存ゲートの組み合わせ

XOR ゲートの表現を AND、NAND、OR を使用して表現してみる。
![xor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3891234/1433c2c9-a022-2161-f218-71879f1b1ad3.png)

上記の図が本当に XOR を実現できているのか真偽値表を埋めてみます。

##### XOR ゲートの真理値表

| $x_1$ | $x_2$ | $s_1$ | $s_2$ | y   |
| ----- | ----- | ----- | ----- | --- |
| 0     | 0     | 1     | 0     | 0   |
| 0     | 1     | 1     | 1     | 1   |
| 1     | 0     | 1     | 1     | 1   |
| 1     | 1     | 0     | 1     | 0   |

$x_1$、$x_2$、y に着目してみると、XOR ゲートの出力になっています。

#### 2.5.2 XOR ゲートの実装

先ほどの XOR ゲートを Go で実装してみます。

```go :xor.go
func (p *perceptron) XOr(x1 float64, x2 float64) float64 {
	s1 := p.NotAnd(x1, x2)
	s2 := p.Or(x1, x2)
	y := p.And(s1, s2)
	return y
}
```

```go:main.go
	fmt.Println("XOR")
	fmt.Println(m.p.XOr(0, 0)) //0を出力
	fmt.Println(m.p.XOr(0, 1)) //1を出力
	fmt.Println(m.p.XOr(1, 0)) //1を出力
	fmt.Println(m.p.XOr(1, 1)) //0を出力
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
    XOR
    0
    1
    1
    0
```

</details>

今回作成した XOR は多層構造になったネットワークでした。
層を複数重ねたパーセプトロンを**多層パーセプトロン**という
※AND、OR などは単層のパーセプトロン

### 2.6 NAND からコンピューターへ

多層パーセプトロンは、これまでの回路より複雑な回路を作成できる。
例）

- 足し算を行う加算器
- 2 進数を 10 進数に変換するエンコーダ
- パリティチェック用の回路
- **コンピュータ**

コンピュータの処理は、NAND ゲートの組み合わせだけで再現できる = パーセプトロンでも、コンピュータを表現できるということ。

### 2.7 まとめ

パーセプトロンはシンプルなので理解事態はしやすいものでした。
コンピュータのような複雑そうな処理をしているものが、意外とパーセプトロンを組み合わせるだけでできるとは驚きでした。。。
パーセプトロン、奥が深いです。

## 次回

### Part.3：**ニューラルネットワーク**