Nxで始めるゼロから作るディープラーニング 準備編

本記事はElixirで機械学習/ディープラーニングができるようになるnumpy likeなライブラリ [Nx](https://github.com/elixir-nx/nx/tree/main/nx)を使って
「[ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装](https://www.oreilly.co.jp/books/9784873117584/)」
をElixirで書いていこうという記事になります。

今回はNxの基本的な使い方や、書籍内で使用されるpyplotやnp.arange,PIL,データセットの取得,pickleファイルの読み込み等のElixirに無い機能の代替品の紹介、実装をしていきます。

[準備編](https://qiita.com/the_haigo/items/1a2f0b371a3644960251)
[exla setup](https://qiita.com/the_haigo/items/d1d1b62bfb9fd2013c17)
1章 pythonの基本 -> とばします
2章 パーセプトロン -> とばします
3章 [ニューラルネットワーク](https://qiita.com/the_haigo/items/bedd466142aaaf01641c)
[with exla](https://qiita.com/the_haigo/items/4ec791c878fd48b9dec1)
４章 [ニューラルネットワークの学習](https://qiita.com/the_haigo/items/2b7535bcff8b4df50d9d)
５章 [誤差逆伝播法](https://qiita.com/the_haigo/items/f273a9c687302507d79d)
[Nx.Defn.Kernel.grad](https://qiita.com/thehaigo/items/593926922a1d72591c3d)
6章 学習に関するテクニック -> とばします
7章 [畳み込みニューラルネットワーク](https://qiita.com/thehaigo/items/a4be7812b97bdddd6c1c/)

# Nx install
Nxのインストールや使ってみたに関しては以下を参考にしてください
[Elixirの革新的ライブラリ「Nx」をMacでも動かしてみた](https://qiita.com/mokichi/items/1716b75709559b18ef6c)
[Elixirでディープラーニング①：革新的ライブラリ「Nx」の導入](https://qiita.com/piacerex/items/db33fbe80751fdd30e48)

※ 2022/04/16追記
最新版はmix deps.getで問題なくinstallできるようになっているので、
空のプロジェクトを作って、実際に動かしてみましょう

```
mix new nx_dl
cd nx_dl
```

```mix.exs
defmodule NxDl.MixProject do
  ...
  defp deps do
    [
      {:nx, "~> 0.1"} #追加
    ]
  end
end
```
以下のコマンドでライブラリを取得し、コンパイルを行います

```
mix deps.get
mix deps.compile
```
問題なく完了したらインタラクティブモードで実行します

```
iex -S mix
```
# Nxの基本
#### テンソル作成

```elixir
# scalar
iex(1)> Nx.tensor(1)
#Nx.Tensor<
  s64
  1
>
iex(2)> Nx.tensor([1,2,3])                  
#Nx.Tensor<
  s64[3]
  [1, 2, 3]
>
# 多重配列
iex(3)> Nx.tensor([[1,2,3],[4,5,6]])
#Nx.Tensor<
  s64[2][3]
  [
    [1, 2, 3],
    [4, 5, 6]
  ]
>

```

#### 四則演算 + dot
broadcastをしてくれますが、ちゃんとNx.tensorでテンソル化する必要がありますので
~~`Nx.add(Nx.tensor([1,2,3]), 1)`といったコードは書けません~~
0.1ではできるようになています

```elixir
# 足し算
Nx.add(Nx.tensor([1,2,3]), Nx.tensor([4,5,6])) 
#Nx.Tensor<
  s64[3]
  [5, 7, 9]
>

Nx.add(Nx.tensor([1,2,3]), 1)      
#Nx.Tensor<
  s64[3]
  [2, 3, 4]
>

# 引き算
Nx.subtract(Nx.tensor([1,2,3]), 1) 
#Nx.Tensor<
  s64[3]
  [0, 1, 2]
>

# 掛け算
Nx.multiply(Nx.tensor([1,2,3]), 2)
#Nx.Tensor<
  s64[3]
  [2, 4, 6]
>

# 割り算
Nx.divide(Nx.tensor([2,4,6]), 2)  
#Nx.Tensor<
  f64[3]
  [1.0, 2.0, 3.0]
>

# dot
Nx.dot(Nx.tensor([[1,2,3],[4,5,6]]), Nx.tensor([1,2,3]))
#Nx.Tensor<
  s64[2]
  [14, 32]
>
```

#### その他よく使うもの
hexdocsがまだないのでgithubのコードのサンプルを読むか
https://github.com/elixir-nx/nx/blob/main/nx/lib/nx.ex
Nx. まで打ってtabキーを押すと定義されている関数の一覧が出てくるのでそこから探すといいかもしれません

```elixir
# transpose
Nx.transpose(Nx.tensor([[1,2,3],[4,5,6]]))
#Nx.Tensor<
  s64[3][2]
  [
    [1, 4],
    [2, 5],
    [3, 6]
  ]
>
# argmax
Nx.argmax(Nx.tensor([4,5,6]))
#Nx.Tensor<
  s64
  2
>
Nx.argmax(Nx.tensor([[1,2,3],[4,5,6]],names: [:x, :y]), axis: :y) 
#Nx.Tensor<
  s64[x: 2]
  [2, 2]
>
# reshape
Nx.reshape(Nx.tensor([[1,2,3],[4,5,6]]),{3,2})
#Nx.Tensor<
  s64[3][2]
  [
    [1, 2],
    [3, 4],
    [5, 6]
  ]
>
# negate
Nx.negate(Nx.tensor([1,2,3]))                 
#Nx.Tensor<
  s64[3]
  [-1, -2, -3]
>

```

# 代替品
## pyplot + numpy.arange
書籍のpython入門の1.6.1 簡単なグラフの描写で使用する pyplotとnumpy.arangeの代替の紹介

pyplotの代わりは[expyplot](https://github.com/MaxStrange/expyplot)を使います
これはpyplotのapiなので使い方はほぼ同じです
https://hexdocs.pm/expyplot/Expyplot.Plot.html

次にnumpy.arangeはNxには実装されていないのでこちらはUtilモジュールを作ってそこに実装していきましょう

```util.ex
defmodule Util do
  def arange(s, e, f) do
    {step, _} = Float.to_string((e / f)) |> Integer.parse()
    Enum.map(0..step, fn i -> s + (f * i) end) |> Nx.tensor()
  end
end
```
上記のコードは s = 数の初め, e = 数の終わり, 間隔 = fとして
(e/f) - (s/f)で要素数をだして、rangeにできるように整数型へ変換し、次の行でf刻みのリストを作成してテンソル化しています

これで準備ができたので1.6.1 簡単なグラフの描写のsinカーブのグラフを描画しましょう

```elixir
alias Expyplot.Plot
x = Util.arange(0,6,0.1)
y = Nx.sin(x)
Plot.plot([Nx.to_flat_list(x), Nx.to_flat_list(y)])
Plot.show()
```
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/7059bd66-3a43-f71c-a2df-dcb24c51c070.png)


matplotlibで問題なくグラフを描画できました
## dataset load & PIL

データセットの読み込みは以下を参考に作成しましたとりあえずmnistだけになります
https://github.com/sasagawa888/deeppipe2/blob/master/lib/mnist.ex

```dataset.ex
defmodule Dataset do
  def download(:mnist) do
    Application.ensure_all_started(:inets)
    file = [
      'train-images-idx3-ubyte.gz',
      'train-labels-idx1-ubyte.gz',
      't10k-images-idx3-ubyte.gz',
      't10k-labels-idx1-ubyte.gz'
    ]
    Mix.shell().cmd("mkdir mnist")
    Enum.each(file, fn f -> get_mnist(f) end)
    :ok
  end

  def get_mnist(file) do
    base_url = 'http://yann.lecun.com/exdb/mnist/'
    {:ok, resp} =
      :httpc.request(:get, {base_url ++ file, []}, [],
        body_format: :binary
      )

    {{_, 200, 'OK'}, _headers, body} = resp

    File.write!("mnist/#{file}", body)
    Mix.shell().cmd("gzip -d mnist/#{file}")
  end

  def train_label(:mnist) do
    {:ok, <<0, 0, 8, 1, 0, 0, 234, 96, label::binary>>} =
      File.read("mnist/train-labels-idx1-ubyte")
    label |> String.to_charlist()
  end

  def train_image() do
    {:ok, <<0, 0, 8, 3, 0, 0, 234, 96, 0, 0, 0, 28, 0, 0, 0, 28, image::binary>>} =
      File.read("mnist/train-images-idx3-ubyte")
    image |> :binary.bin_to_list() |> Enum.chunk_every(784)
  end

  def test_label() do
    {:ok, <<0, 0, 8, 1, 0, 0, 39, 16, label::binary>>} = File.read("mnist/t10k-labels-idx1-ubyte")

    label |> String.to_charlist()
  end

  def test_image() do
    {:ok, <<0, 0, 8, 3, 0, 0, 39, 16, 0, 0, 0, 28, 0, 0, 0, 28, image::binary>>} =
      File.read("mnist/t10k-images-idx3-ubyte")

    image |> :binary.bin_to_list() |> Enum.chunk_every(784)
  end
end

```
PIL(Python Image Library)ですが、bitmapから画像を生成して表示する用途して使うだけですので
bitmapから標準出力で画像のように表示するNxのheatmapで代用できます

```elixir
Dataset.test_image() |> List.first |> Nx.tensor() |> Nx.reshape({28,28}) |> Nx.to_heatmap
#Nx.Heatmap<
  s64[28][28]
>
```
![スクリーンショット 2021-02-20 3.00.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/9bd457e3-f40e-c7ed-8697-b46c45c0b653.png)

# pklファイルの読み込み
最後にpklファイルの読み込みになります
書籍で使用しているpklファイルはndarrayのためbitstringにしてもlistに変換できないので、
pythonを呼び出してファイルを読み込み elixirのデータに変換して使用します

ライブラリには[ErlPort](https://github.com/hdima/erlport)を使用します
こちらはerlangのライブラリですがelixirを記述している言語なので問題なく使用できます

pythonコードを実行し、結果を受け取るelixirコードとpklからデータを読み込んで値を返すpythonコードを記述します

```pkl_load.ex
defmodule PklLoad do
  def load(file) do
    { :ok, py_exec } = :python.start([ python_path: 'pkl'])
    result = :python.call( py_exec, :pkl_load, :load, [file])
    :python.stop( py_exec)
    result
  end
end
```

```pkl/pkl_load.py
import numpy
import pickle
def load(file):
    with open(file, 'rb') as f:
        pkl = pickle.load(f)
    w1,w2,w3 = pkl['W1'].tolist(), pkl['W2'].tolist(), pkl['W3'].tolist()
    b1,b2,b3 = pkl['b1'].tolist(), pkl['b2'].tolist(), pkl['b3'].tolist()
    return w1,w2,w3,b1,b2,b3

```

使用する際には読み込むpklファイルをpklフォルダの中に入れて置いてください

```elixir
{w1,w2,w3,b1,b2,b3} = PklLoad.load("pkl/sample_weight.pkl")
```

# 最後に
これで書籍で使う物がある程度揃ったので、めっちゃ頑張ればElixir製のDeepLeaningライブラリができるかもしれません！

GPUを使用しての学習は[exla](https://github.com/elixir-nx/nx/tree/main/exla)を使用すればできるらしいので
今後に期待です！

本記事は以上になりますありがとうございました
# コード
https://github.com/thehaigo/nx_dl

# 参考ページ
https://github.com/elixir-nx/nx/tree/main/nx
https://github.com/elixir-nx/nx/tree/main/exla
https://www.oreilly.co.jp/books/9784873117584/
https://qiita.com/mokichi/items/1716b75709559b18ef6c
https://qiita.com/piacerex/items/db33fbe80751fdd30e48
https://github.com/MaxStrange/expyplot
https://hexdocs.pm/expyplot/Expyplot.Plot.html
https://github.com/sasagawa888/deeppipe2/blob/master/lib/mnist.ex
https://github.com/hdima/erlport
https://qiita.com/piacerex/items/c1af7b6ce472db83cff6