ゼロから作る Deep LearningをElixirで学ぶシリーズ ~ Numpy -> Nx に置き換えて 1章やる~

東京にいるけど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で学ぶディープラーニングの理論と実装

最初の方はPythonについての説明なので、1.5のNumpyまで読み飛ばします。

## PythonのNumpy -> ElixirのNx ?
所感です。違ってたら詳しい人ご指摘ください。

ディープラーニングの実装では、配列や行列の計算が多く、それをサポートするためのライブラリがNumpyだそうです。であれば、Elixirで言うところのNxがNumpyと同じなのかな？

と今の所は思ってます。

とりあえず、読み進めながらElixirに置き換えていきます。
また、Nxの勉強も同時にこちらでしています。

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

https://qiita.com/Yoosuke/items/193619975341e560a8f9

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


## 1.5.2 Numpy 配列の生成 を Nxでやる
ここでは、とりあえず ```[1, 2, 3]```のような配列を作っています。
Nxで同じように作ってみることにします。

```elixir
x = [1.0, 2.0, 3.0] 
|> Nx.tensor(names: [:x])
#Nx.Tensor<
  f32[x: 3]
  [1.0, 2.0, 3.0]
>

iex()> x[x: 0..2]
#Nx.Tensor<
  f32[x: 3]
  [1.0, 2.0, 3.0]
>
```

## 1.5.3 NumPyの算術計算 を Nxでやる

要するに、線形代数のベクトルの和みたいな事ができれば良いようです。

```[1.0, 2.0, 3.0] + [2.0, 4.0, 6.0] = [3.0, 6.0, 9.0]```
みたいな事がしたいわけですね。

Nxでやるにはどうすればいいのか？ 
```Nx.add```でできそうです。 以下、例


```elixir
iex()> left = Nx.tensor([1.0,2.0,3.0], names: [:y])     
#Nx.Tensor<
  f32[y: 3]
  [1.0, 2.0, 3.0]
>
iex()> right = Nx.tensor([2.0,4.0,6.0], names: [:y])
#Nx.Tensor<
  f32[y: 3]
  [2.0, 4.0, 6.0]
>
iex()> Nx.add(left, right)
#Nx.Tensor<
  f32[y: 3]
  [3.0, 6.0, 9.0]
>
```

次に、差を求めたい場合。

```[1.0, 2.0, 3.0] - [2.0, 4.0, 6.0] = [-1.0, -2.0, -3.0]```
```Nx.subtract```を使えば出来ました。

```elixir
 Nx.subtract(left, right)
#Nx.Tensor<
  f32[y: 3]
  [-1.0, -2.0, -3.0]
>
```
掛け算 

スカラー倍 
いわゆるベクトルとスカラーの積
```[1.0, 2.0, 3.0] * 2 = [2.0, 4.0, 6.0]```

ベクトル と ベクトルの積
```[1.0, 2.0, 3.0] * [2.0, 4.0, 6.0] = [2.0, 8.0, 18.0]```


```elixir
iex()> Nx.multiply(left, 2)                     
#Nx.Tensor<
  f32[y: 3]
  [2.0, 4.0, 6.0]
>
iex()> Nx.multiply(left, right)
#Nx.Tensor<
  f32[y: 3]
  [2.0, 8.0, 18.0]
>
```

割り算 

スカラー倍 
いわゆるベクトルとスカラーの商
```[1.0, 2.0, 3.0] / 2 = [0.5, 1.0, 1.5]```

ベクトル と ベクトルの商
```[1.0, 2.0, 3.0] / [2.0, 4.0, 6.0] = [0.5, 0.5, 0.5]```



```elixir
iex()> Nx.divide(left, 2)    
#Nx.Tensor<
  f32[y: 3]
  [0.5, 1.0, 1.5]
>

iex()> Nx.divide(left, right)
#Nx.Tensor<
  f32[y: 3]
  [0.5, 0.5, 0.5]
>
```

## 1.5.4 NumpyのN次元配列 を Nxでやる

行列の形状は```shape```で、行列の要素のデータ型は```type```で確認できます。

```elixir
iex()> tensor2x2 = Nx.tensor([[1,2],[3,4]])
#Nx.Tensor<
  s64[2][2]
  [
    [1, 2],
    [3, 4]
  ]
>
iex()> Nx.shape(tensor2x2)
{2, 2}

iex()> Nx.type tensor2x2
{:s, 64}

```

## 1.5.5 ブロードキャスト

形状の異なる配列の演算を可能にするブロードキャスト
2x2 の形状の行列に 10というスカラー値を計算した場合 以下のように計算して欲しい。

```elixir
[[1, 2],             [[10, 20],
 [3, 4]]  *  10  =    [30, 40]]
```

これを実現するのがブロードキャスト

```elixir
# 10 -> [[10, 10],
#        [10, 10]] 
iex()> Nx.broadcast(10, {2,2})
#Nx.Tensor<
  s64[2][2]
  [
    [10, 10],
    [10, 10]
  ]
>
```
ちなみに、Nxの四則演算を実現するものは、自然とブロードキャストされて計算されるようです。

```elixir
iex()> tensor2x2 = Nx.tensor([[1,2],[3,4]])
#Nx.Tensor<
  s64[2][2]
  [
    [2, 3],
    [4, 5]
  ]
>
iex()> Nx.add(tensor2x2, 10)
#Nx.Tensor<
  s64[2][2]
  [
    [11, 12],
    [13, 14]
  ]
>
```
下記は、1次元配列と2次元配列の計算で1次元配列をブロードキャストして、計算しています。

```elixir
a = Nx.tensor([[1,2],[3,4]])
b = Nx.broadcast(Nx.tensor([1,2]), {2, 2})
Nx.multiply(a, b)
#Nx.Tensor<
  s64[2][2]
  [
    [1, 4],
    [3, 8]
  ]
>
```

## 1.5.6 要素へのアクセス

要素のインデックスは0から始まります。

```elixir
iex()> x = Nx.tensor([[51, 55], [14, 19], [0, 4]])
#Nx.Tensor<
  s64[3][2]
  [
    [51, 55],
    [14, 19],
    [0, 4]
  ]
>
iex()> x[0]
#Nx.Tensor<
  s64[2]
  [51, 55]
>
iex()> x[0][1]
#Nx.Tensor<
  s64
  55
>
```

配列にして各要素にアクセスする方法
Pythonの方では、```.flatten()```で配列化してアクセス可能らしいです。
Nx.にもflattenメソッドがあるので、これで同じような事ができるのでしょうか？

```elixir
iex()> Nx.flatten(tensor2x2)
#Nx.Tensor<
  s64[4]
  [1, 2, 3, 4]
>
```

試してみた所、Tensorの構造体における1次元配列化のようでした。
これをEnumで処理できる形に（リスト化)する場合には、```Nx.to_flat_list```を利用するようです。

```elixir
iex()> Nx.flatten(tensor2x2) |> Nx.to_flat_list
[1, 2, 3, 4]
```

こうする事で、各要素に対して処理を実装することができました。

```elixir
iex()> Nx.flatten(tensor2x2)
|> Nx.to_flat_list
|> Enum.map(fn x -> Nx.add(x,2) end)

[
  #Nx.Tensor<
    s64
    3
  >,
  #Nx.Tensor<
    s64
    4
  >,
  #Nx.Tensor<
    s64
    5
  >,
  #Nx.Tensor<
    s64
    6
  >
]
```

## Matplotlib 

pyplotモジュールのElixir版？というか、ラッパーモジュールが2つ見つかりました。

[Explot](https://github.com/JordiPolo/explot)、[Expyplot](https://github.com/MaxStrange/expyplot)、でドキュメントを読むにExpyplotはExplotに触発されて作ったと書かれていたので、今回は、Expyplotを利用したいと思います。

Expyplot は ```PyQt5``` と ```matplotlib``` をインストールしてくださいと書かれているのでインストールする。が少々、ハマったのでそちらも記載しておきます。
 
> You must also pip3 install PyQt5 and matplotlib: pip3 install PyQt5, pip3 install matplotlib, or however you install packages for python3 on your particular system.

結論から言うと、以下でインストールしました。```python3 -m pip install PyQt5```こちらでインストールすると変なErrorが出ます。（by 環境 M1 Mac）

```zsh
$ brew install pyqt@5
$ pip3 install matplotlib
```


ちなみに、こちらを探している所で、 @the_haigo [Nxで始めるゼロから作るディープラーニング 準備編](https://qiita.com/the_haigo/items/1a2f0b371a3644960251)という記事を見つけました。ちなみに、このシリーズも @the_haigo さんがイベント時にElixirのコードが無いのが勉強になったと言っていたのをキッカケに僕も学習し始めたので、なるべく見ないで、詰まったら答え合わせ用に拝見させていただきます！ 

### 単純なグラフの描画
* データの作成
* 0から6まで0.1刻みでデータを生成 Numpyのarange関数を利用しているのですが、なんとなくイメージできますが、細かい仕様がわからないので、Numpyのライブラリを確認してみます。[NumPy.arange](https://numpy.org/doc/stable/reference/generated/numpy.arange.html)

日本語の機械翻訳が少しおかしいですが、何となく読みます。w
> * arangeさまざまな数の位置引数で呼び出すことができます。
> * arange(stop): 半開間隔 (つまり、startを含み、 stopを除く間隔) 内で値が生成されます。[0, stop)
> * arange(start, stop)：ハーフオープン区間内で値が生成されます。[start, stop)
> * arange(start, stop, step)値は、 で与えられる値間の 間隔で、半開間隔 内で生成されます。[start, stop)step

で、この仕様を元にElixirで関数を作り始めたものの、汎用的にしようと作成したら軽く（意外に少数の桁数も入力したら範囲指定可能なようにしてたらそこそこボリュームあるが、面白いのでついつい沼にハマって）、こちらの学習が進まないのでこの関数の完成はまた後で継続的にするとして、ここではサクッとする事にしました。（PS, 以下のも汎用的に使うにはバグが潜んでるのですが、今回の例には問題ないです）

```elixir
defmodule Nxnp do
  def arange(stop) when is_integer(stop) do
    0..stop
  end

  def arange(stop) when is_float(stop) and (stop >= 0.1) and (stop <= 9.9) do
      string_float = Float.to_string(stop)
      <<head,46,tail::binary>> = string_float
      start = <<head>> |> String.to_integer

      num = String.codepoints(tail) |> length()
      point_list  = Enum.map(1..num, fn _ -> 0.1 end)
      digit = Enum.reduce(point_list, fn x, acc -> x * acc end)

      e = String.to_integer(tail)
      finish = (start * 10) + e
      Enum.map(0..finish, fn x -> Float.round(x * digit, num) end)
  end
end
```

という事で、以下の関数を作る過程で先に、作成していた関数でも今回必要なデータが作れるのでこちらはその内作ります。

```elixir
iex()> Nxnp.arange(0, 1, 0.1)
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
```

と言うことで簡単なグラフの生成をしていきます。

```elixir
iex()> x = Nxnp.arange(6.0) |> Nx.tensor |> Nx.flatten
iex()> y = Nx.sin(x) 
iex()> alias Expyplot.Plot
iex()> Plot.plot([Nx.to_flat_list(x), Nx.to_flat_list(y)])
iex()> Plot.show()
```

![sin_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72962/c4450913-4ec5-25a6-0b17-00bf06e0ae7c.png)

画像にタイトルやら何やらつける

```elixir
iex()> Plot.plot([Nx.to_flat_list(x), Nx.to_flat_list(y1)], label: "sin")
"[<matplotlib.lines.Line2D object at 0x150e97580>]"
iex()> Plot.plot([Nx.to_flat_list(x), Nx.to_flat_list(y2)], label: "cos", linewidth: 0.5)
"[<matplotlib.lines.Line2D object at 0x150e97700>]"
iex()> Plot.xlabel("x")
"Text(0.5, 0, 'x')"
iex()> Plot.xlabel("y")
"Text(0.5, 0, 'y')"
iex()> Plot.title("sin & cos")
"Text(0.5, 1.0, 'sin & cos')"
iex()> Plot.legend
"Legend"
iex()> Plot.show
```

![sin&cos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72962/3c4748c2-c0c9-0d17-25a7-5e1933fc03f6.png)

と言うことで今回はここまで。

PS, 1.6.3 画像の表示は 割愛

次回は、2章パーセプトロンを学びます。

https://qiita.com/Yoosuke/items/751a9fe05b89755e25a8