ゼロから作る Deep LearningをElixirで学ぶシリーズ ~ Perceptron（パーセプトロンの実装） ~

東京にいるけどfukuokaexのYOSUKEです。
最近、[エリクサーちゃんで学ぶ Elixir](https://youtu.be/_QzrB6tTPwA)の動画を作成し始めてるので良かったらチャンネル登録お願いします。本格的に来週後半からの予定です。

ゼロから作る Deep Learningという本を見ながら Elixirの計算ライブラリ系を学ぶシリーズです。
正直このアウトプットは試行錯誤を書き連ねる予定なので、手っ取り早く知りたいって方は、学習し終えたらものからまとめ書くつもりなのでそれまでお待ちください。m(_ _)m

## 学習開始
この書籍を元にElixirで学びます。

<a target="_blank" href="https://www.amazon.co.jp/%25E3%2582%25BC%25E3%2583%25AD%25E3%2581%258B%25E3%2582%2589%25E4%25BD%259C%25E3%2582%258BDeep-Learning-%25E2%2580%2595Python%25E3%2581%25A7%25E5%25AD%25A6%25E3%2581%25B6%25E3%2583%2587%25E3%2582%25A3%25E3%2583%25BC%25E3%2583%2597%25E3%2583%25A9%25E3%2583%25BC%25E3%2583%258B%25E3%2583%25B3%25E3%2582%25B0%25E3%2581%25AE%25E7%2590%2586%25E8%25AB%2596%25E3%2581%25A8%25E5%25AE%259F%25E8%25A3%2585-%25E6%2596%258E%25E8%2597%25A4-%25E5%25BA%25B7%25E6%25AF%2585/dp/4873117585?&_encoding=UTF8&tag=edtechfather-22&linkCode=ur2&linkId=5100d6b10a6078f983516dfc20f685cc&camp=247&creative=1211">ゼロから作るDeep Learning</a> ―Pythonで学ぶディープラーニングの理論と実装

と言う事で、今回は 第2章のパーセプトロンを学びます。
内容は、書籍に任せることにして、Elixirでの実装部分を学習しながら進めたいと思います。

前回の学習内容はこちらです。

https://qiita.com/Yoosuke/items/8599ed0a976de1371ead

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

* パーセプトロンとは
> 複数の信号を入力として受け取り、一つの信号を出力します。

と言う事で、論理演算のイメージみたいです。ただし、0と1の重みづけの際に、0と判断するのか、1と判断するのかの閾値を決める必要があり、論理演算の場合は人間があらかじめ決めた閾値の通りに動くのを、機械学習の問題ではこの閾値を決める作業をコンピュータに自動で行わせる作業で、人がやることはパーセプトロンの構造を考えることにあると思います。

### 2.3.1 簡単な実装

#### 準び
```elixir
$ mix new perceptron
```

#### ANDゲートの実装

```lib/perceptron.ex
defmodule Perceptron do
  def _and(x1, x2) do
    [w1, w2, theta] = [0.5, 0.5, 0.7]
    tmp = x1 * w1 + x2 * w2
    case tmp do
      tmp when tmp > theta -> 1
      tmp when tmp <= theta -> 0
    end
  end
end
```
ANDゲートの実行結果
```elixir
iex()> Perceptron._and(1,0)
0
iex()> Perceptron._and(0,0)
0
iex()> Perceptron._and(0,1)
0
iex()> Perceptron._and(1,1)
1
```

#### NANDゲートの実装
NANDゲートはANDゲートの実装のパラメータを反転する事で実現できるので、反転してみました。

```elixir
  def _nand(x1, x2) do
    [w1, w2, theta] = [-0.5, -0.5, -0.7]
    tmp = x1 * w1 + x2 * w2
    case tmp do
      tmp when tmp > theta -> 1
      tmp when tmp <= theta  -> 0
    end
  end
```

NANDゲートの実行結果

```elixir
iex()> Perceptron._nand(1,1)
0
iex()> Perceptron._nand(0,1)
1
iex()> Perceptron._nand(0,0)
1
iex()> Perceptron._nand(1,0)
1
```

#### ORゲートの実装
ORゲートは、どちらか1つが1であれば1になり0になるのはどちらも0の場合のみなのでパラメータのthetaを0にすればOKです。

```elixir
  def _or(x1, x2) do
    [w1, w2, theta] = [0.5, 0.5, 0.0]
    tmp = x1 * w1 + x2 * w2
    case tmp do
      tmp when tmp > theta -> 1
      tmp when tmp <= theta  -> 0
    end
  end
```

実は、ここまで書くまでに最初、少しだけ、上記のプログラムを変更して書いてました。

例えば、NANDゲートに関しては、こんな感じで、ここでこのプログラムは同じ構造であることが重要なんだと気がつきました。要するにパラメータを変えるだけで挙動が変わる、構造は同じモデルである事が重要なのだと。そこで、```Enum.map(& &1 * -1)```の余計な処理を削除して、パラメータを変更する形に変えました。

```elixir
  def _nand(x1, x2) do
    [w1, w2, theta] = [0.5, 0.5, 0.7] |> Enum.map(& &1 * -1)
    tmp = x1 * w1 + x2 * w2
    case tmp do
      tmp when tmp > theta -> 1
      tmp when tmp <= theta  -> 0
    end
  end
```

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

Nxを使うので、Nxモジュールをインストールします。
```mix.exs
  defp deps do
    [
      {:nx, "~> 0.4.1"}
    ]
  end
```

```zsh
$ mix deps.get
```
wを重み、bをバイアスとしてθを-bにして式変換してANDを実装する、まずはインタラクティブモードで実行しながら確認します。

```elixir
iex()> x = Nx.tensor([0,1])
#Nx.Tensor<
  s64[2]
  [0, 1]
>
iex()> w = Nx.tensor([0.5,0.5])
#Nx.Tensor<
  f32[2]
  [0.5, 0.5]
>
iex()> b = -0.7
-0.7
iex()> Nx.multiply(x, w)
#Nx.Tensor<
  f32[2]
  [0.0, 0.5]
>
iex()> Nx.multiply(x, w)|> Nx.sum 
#Nx.Tensor<
  f32
  0.5
>
iex()>  Nx.multiply(x, w)|> Nx.sum |> Nx.add(b)
#Nx.Tensor<
  f32
  -0.19999998807907104
>
```

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

#### ANDゲートの実装
```elixir
  def nx_and(x1, x2) do
    x = Nx.tensor([x1, x2])
    w = Nx.tensor([0.5, 0.5])
    b = -0.7
    [tmp] =  Nx.multiply(x, w)
    |> Nx.sum
    |> Nx.add(b)
    |> Nx.to_flat_list()
    case tmp do
      tmp when tmp <= 0  -> 0
      tmp when tmp > 0 -> 1
    end
  end
```
wの重みづけと違い、bのバイアスは1の出力する度合いの調整をする役割を持っている。

#### NANDゲートの実装
ANDのバイアスと重みのパラメータを反転するだけで実装できます。

```elixir
  def nx_nand(x1, x2) do
    x = Nx.tensor([x1, x2])
    w = Nx.tensor([-0.5, -0.5])
    b = 0.7
    [tmp] =  Nx.multiply(x, w)
    |> Nx.sum
    |> Nx.add(b)
    |> Nx.to_flat_list()
    case tmp do
      tmp when tmp <= 0  -> 0
      tmp when tmp > 0 -> 1
    end
  end
```

#### ORゲートの実装
バイアスの重みづけを0にするだけで変更できます。

```elixir
  def nx_or(x1, x2) do
    x = Nx.tensor([x1, x2])
    w = Nx.tensor([0.5, 0.5])
    b = 0
    [tmp] =  Nx.multiply(x, w)
    |> Nx.sum
    |> Nx.add(b)
    |> Nx.to_flat_list()
    case tmp do
      tmp when tmp <= 0  -> 0
      tmp when tmp > 0 -> 1
    end
  end
```

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

パーセプトロンを用いる事で、AND, OR, NANDを同じ構造で実装できました。そこで、次はXORについてみて行きます。

### 2.4.1 XORゲート

| x1 | x2 | y |
|:-:|:-:|:-:|
|  0 | 0  | 0  |
|  1 | 0  | 1  |
|  0 | 1  | 1  |
|  1 | 1  | 0  |

ここでの学習メモ：
AND, OR, NANDは線形な領域で直線で0と１の領域を分けられたが、XORの場合は線形では分けられず、
非線形な領域での分別が必要な為、パーセプトロンでは分けられない。
しかし、層を重ねるというアイデアを駆使すればXORも分別可能になる。

| x1 | x2 | s1(NAND) | s2(OR) | y(AND) |
|:-:|:-:|:-:|:-:|:-:|
| 0  | 0  | 1  | 0  | 0  |
| 1  | 0  | 1  | 1  | 1  |
| 0  | 1  | 1  | 1  | 1  |
| 1  | 1  | 0  | 1  | 0  |

という事で、ここまで書いたら実装は簡単ですね。

```elixir
  def nx_xor(x1, x2) do
    s1 = nx_nand(x1, x2)
    s2 = nx_or(x1, x2)
    nx_and(s1,s2)
  end
```
XORは2つのパーセプトロンを介して解を出しているので、2層のパーセプトロンというらしい。
そして、この多重の層を重ねたパーセプトロンならもっと複雑な事も可能になるのと、次章のニューラルネットワークの基礎になるので重要との事でした。

よし、いよいよ次章、ニューラルネットワークに入っていける。楽しみだ！

https://qiita.com/Yoosuke/items/bc65b93e7e4dc1f0002d