LoginSignup
13
6

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-01-03

東京にいるけどfukuokaexのYOSUKEです。
最近、エリクサーちゃんで学ぶ Elixirの動画を作成し始めてるので良かったらチャンネル登録お願いします。本格的に始動するのは年明けからの予定です。

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

学習開始

この書籍を元にElixirで学びます。

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

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

PythonのNumpy -> ElixirのNx ?

所感です。違ってたら詳しい人ご指摘ください。

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

と今の所は思ってます。

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

1.5.2 Numpy 配列の生成 を Nxでやる

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

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でできそうです。 以下、例

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を使えば出来ました。

 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]

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]

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で確認できます。

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というスカラー値を計算した場合 以下のように計算して欲しい。

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

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

# 10 -> [[10, 10],
#        [10, 10]] 
iex()> Nx.broadcast(10, {2,2})
#Nx.Tensor<
  s64[2][2]
  [
    [10, 10],
    [10, 10]
  ]
>

ちなみに、Nxの四則演算を実現するものは、自然とブロードキャストされて計算されるようです。

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次元配列をブロードキャストして、計算しています。

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から始まります。

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メソッドがあるので、これで同じような事ができるのでしょうか?

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

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

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

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

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つ見つかりました。

ExplotExpyplot、でドキュメントを読むにExpyplotはExplotに触発されて作ったと書かれていたので、今回は、Expyplotを利用したいと思います。

Expyplot は PyQt5matplotlib をインストールしてくださいと書かれているのでインストールする。が少々、ハマったのでそちらも記載しておきます。

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)

$ brew install pyqt@5
$ pip3 install matplotlib

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

単純なグラフの描画

  • データの作成
  • 0から6まで0.1刻みでデータを生成 Numpyのarange関数を利用しているのですが、なんとなくイメージできますが、細かい仕様がわからないので、Numpyのライブラリを確認してみます。NumPy.arange

日本語の機械翻訳が少しおかしいですが、何となく読みます。w

  • arangeさまざまな数の位置引数で呼び出すことができます。
  • arange(stop): 半開間隔 (つまり、startを含み、 stopを除く間隔) 内で値が生成されます。[0, stop)
  • arange(start, stop):ハーフオープン区間内で値が生成されます。[start, stop)
  • arange(start, stop, step)値は、 で与えられる値間の 間隔で、半開間隔 内で生成されます。[start, stop)step

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

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

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

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]

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

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

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

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

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

PS, 1.6.3 画像の表示は 割愛

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

13
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6