本記事はElixirで機械学習/ディープラーニングができるようになるnumpy likeなライブラリ Nxを使って
「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」
をElixirで書いていこうという記事になります。
今回はNxの基本的な使い方や、書籍内で使用されるpyplotやnp.arange,PIL,データセットの取得,pickleファイルの読み込み等のElixirに無い機能の代替品の紹介、実装をしていきます。
準備編
exla setup
1章 pythonの基本 -> とばします
2章 パーセプトロン -> とばします
3章 ニューラルネットワーク
with exla
4章 ニューラルネットワークの学習
5章 誤差逆伝播法
Nx.Defn.Kernel.grad
6章 学習に関するテクニック -> とばします
7章 畳み込みニューラルネットワーク
Nx install
Nxのインストールや使ってみたに関しては以下を参考にしてください
Elixirの革新的ライブラリ「Nx」をMacでも動かしてみた
Elixirでディープラーニング①:革新的ライブラリ「Nx」の導入
※ 2022/04/16追記
最新版はmix deps.getで問題なくinstallできるようになっているので、
空のプロジェクトを作って、実際に動かしてみましょう
mix new nx_dl
cd nx_dl
defmodule NxDl.MixProject do
...
defp deps do
[
{:nx, "~> 0.1"} #追加
]
end
end
以下のコマンドでライブラリを取得し、コンパイルを行います
mix deps.get
mix deps.compile
問題なく完了したらインタラクティブモードで実行します
iex -S mix
Nxの基本
テンソル作成
# 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ではできるようになています
# 足し算
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キーを押すと定義されている関数の一覧が出てくるのでそこから探すといいかもしれません
# 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を使います
これはpyplotのapiなので使い方はほぼ同じです
https://hexdocs.pm/expyplot/Expyplot.Plot.html
次にnumpy.arangeはNxには実装されていないのでこちらはUtilモジュールを作ってそこに実装していきましょう
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カーブのグラフを描画しましょう
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()
matplotlibで問題なくグラフを描画できました
dataset load & PIL
データセットの読み込みは以下を参考に作成しましたとりあえずmnistだけになります
https://github.com/sasagawa888/deeppipe2/blob/master/lib/mnist.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で代用できます
Dataset.test_image() |> List.first |> Nx.tensor() |> Nx.reshape({28,28}) |> Nx.to_heatmap
#Nx.Heatmap<
s64[28][28]
>
pklファイルの読み込み
最後にpklファイルの読み込みになります
書籍で使用しているpklファイルはndarrayのためbitstringにしてもlistに変換できないので、
pythonを呼び出してファイルを読み込み elixirのデータに変換して使用します
ライブラリにはErlPortを使用します
こちらはerlangのライブラリですがelixirを記述している言語なので問題なく使用できます
pythonコードを実行し、結果を受け取るelixirコードとpklからデータを読み込んで値を返すpythonコードを記述します
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
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フォルダの中に入れて置いてください
{w1,w2,w3,b1,b2,b3} = PklLoad.load("pkl/sample_weight.pkl")
最後に
これで書籍で使う物がある程度揃ったので、めっちゃ頑張ればElixir製のDeepLeaningライブラリができるかもしれません!
GPUを使用しての学習はexlaを使用すればできるらしいので
今後に期待です!
本記事は以上になりますありがとうございました
コード
参考ページ
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