NArrayの簡単なつかい方

RubyにはNumPyがないってよく言われますが、RubyにもNArrayがあるよ。
NArrayの速度をきちんと測定した資料は少ないですが、NumPyとほとんど同等の速度が出せるとされています。
　　　![image.png](https://qiita-image-store.s3.amazonaws.com/0/144608/dacb834a-66f4-23a6-d337-90f474520758.png)　　　　　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/de698f1e-5d1a-4c0e-c7e7-2dcdfd252ce1.png)

# NArrayについて

OSSで開発されているRubyの行列計算ライブラリです。開発者は@masa16さんで、C言語で実装されています。

* [Numo::NArray](https://github.com/ruby-numo/numo-narray)
    * Ruby言語における事実上のNumPy代替のスタンダード
    * 機械学習の[Rumale](https://github.com/yoshoku/rumale)やAnkaneさんのプロジェクトなどに多数採用された。
    * GPGPUに対応した[Cumo](https://github.com/sonots/cumo)もある。
    * 単にNArrayといった場合、前身プロジェクトの[旧NArray](https://github.com/masa16/narray)を意味していることがある。

## リファレンスおよび資料

* ドキュメント： https://www.rubydoc.info/gems/numo-narray

そのほかの公式ドキュメント

* [Numo vs numpy](https://github.com/ruby-numo/numo-narray/wiki/Numo-vs-numpy)
* [Numo::NArray概要](https://github.com/ruby-numo/numo-narray/wiki/Numo::NArray%E6%A6%82%E8%A6%81)
* [100 narray exercises](https://github.com/ruby-numo/numo-narray/wiki/100-narray-exercises)

# NArrayの簡単なつかい方

NArrayの世界は広くて、全てを理解するのはちょっとハードです。
なのでこれさえ知っておけば8割ぐらいの用途をカバーできるかも…といったあたり[^1]を目指してまとめます。

## NArrayの型

Rubyの配列と違い、NArrayの配列には型があります。

|整数|符号なし整数|小数|複素数|
|---|---|---|---|
|Numo::Int8|Numo::UInt8|Numo::SFloat|Numo::SComplex|
|Numo::Int16|Numo::UInt16|Numo::DFloat|Numo::DComplex|
|Numo::Int32|Numo::UInt32|||
|Numo::Int64|Numo::UInt64|||

使用頻度が高いのは、`UInt8` `Int32` `SFloat` `DFloat` あたりでしょうか。

## 前準備
以下すべて`include Numo`を行った状態で書きます。

```ruby
require 'numo/narray'
include Numo
```

`Numo::Int32` は `Int32` と記載するという意味です。簡単に説明するためにそうしています。実際のコードで `include`するべきかは状況によります。

## NArrayの生成

```ruby
require 'numo/narray'
include Numo
```

```ruby
Int32[1,2,3]
```
　[1, 2, 3]
 
```ruby
Int32.new(3,3).seq
```
　[[0, 1, 2], 
　 [3, 4, 5], 
　 [6, 7, 8]]

```ruby 
a = [[0, 1], [2, 3]]
Int32[*a]
```
　[[0, 1], 
　 [2, 3]]


```ruby 
a = [[0, 1], [2, 3]]
Int32.cast(a)
```
　[[0, 1], 
　 [2, 3]]

```ruby
Int32[1..5]
```
　[1, 2, 3, 4, 5]

```ruby
Int32[1...5]
```
　 [1, 2, 3, 4]

## NArrayの四則演算

```ruby
require 'numo/narray'
include Numo
```

```ruby
a = Int32.new(3,3).seq
```
　[[0, 1, 2], 
　 [3, 4, 5], 
　 [6, 7, 8]]

```ruby
b = Int32.new(3,3).seq + 1
```
　[[1, 2, 3], 
　 [4, 5, 6], 
　 [7, 8, 9]]

```ruby
a + b
```
　[[1, 3, 5], 
　 [7, 9, 11], 
　 [13, 15, 17]]

```ruby
b - a
```
　[[1, 1, 1], 
　 [1, 1, 1], 
　 [1, 1, 1]]

```ruby
a * b
```
　[[0, 2, 6], 
　 [12, 20, 30], 
　 [42, 56, 72]]

```ruby
a * 2
```
　[[0, 2, 4], 
　 [6, 8, 10], 
　 [12, 14, 16]]

```ruby
a * 0.1
```
　[[0, 0.1, 0.2], 
　 [0.3, 0.4, 0.5], 
　 [0.6, 0.7, 0.8]]
　 キャストが自動的に行われる

## 型を変換する
計算中に自動ででキャストされることが多いのですが、明示的にやる場合は、

```ruby
a = UInt8[1,2,3]
SFloat.cast(a)
# => Numo::SFloat#shape=[3]
```
とします。ここではUInt8をSFloatに変換している。

## 行列積
ニューラルネットワークによく出てくる計算

```ruby
c = Int32.new(2,2).seq
 
# [[0, 1], 
#  [2, 3]]
 
d = Int32.new(2,1).seq
 
# [[0], 
#  [1]]
 
c.dot d
 
# [[1], 
#  [3]]
```

より高速に計算したい場合は、Numo::Linalgを使います。

* 参考Qiita記事：[numo-linalgをmacで動かしてみた](https://qiita.com/daddygongon/items/73a83565b7969ffc5d1a)

```ruby
Numo::Linalg.dot(c,d)
Numo::Linalg.matmul(c,d)
# [[1], 
#  [3]]
```

## いろいろな行列を生成する
ゼロ行列

```ruby 
x = Int32.zeros(3,3)
```
　[[0, 0, 0], 
　 [0, 0, 0], 
　 [0, 0, 0]]

全要素が１の行列

```ruby
x = Int32.ones(3,3)
```
　[[1, 1, 1], 
　 [1, 1, 1], 
　 [1, 1, 1]]

全要素が２の行列

```ruby
z = y.fill 2
```

　[[2, 2, 2], 
　 [2, 2, 2], 
　 [2, 2, 2]]

単位行列

```ruby
y = Int32.eye(3,3)
```

　[[1, 0, 0], 
　 [0, 1, 0], 
　 [0, 0, 1]]

等間隔の行列

```ruby
f = DFloat.linspace(-20,20,11)
```
 [-20, -16, -12, -8, -4, 0, 4, 8, 12, 16, 20]


## NArrayの情報を取得する
```ruby
e = DFloat.new(3,4).seq
 
# 各次元
e.shape              # => [3, 4]
 
# 要素の数
e.size               # => 12
 
# バイトサイズ
e.byte_size          # => 96
 
# 次元数
e.rank               # => 2 #e.ndim も同じ
```

## 文字列との相互変換
外部のツールと情報をやりとりする時に、文字列へ変換するときがあります。

```ruby
s = UInt8.ones(2,2).to_string
# => "\x01\x01\x01\x01"
# [[1, 1], 
#  [1, 1]]
 
UInt8.from_string(d)
# => Numo::UInt8#shape=[4]
# [1, 1, 1, 1]
# 次元数の情報は含まれないので注意
```

NArrayの行列（画素）も文字列に変換すれば、TkやGtkなどのGUIツールキットと接続できるので、リアルタイムな何かを作るときに便利です。

## Ruby Arrayへの変換
`to_a`で大丈夫です

```ruby
a = UInt8[1,2,3]

a.to_a 
```

## NMath 
`sin` `cos` `tan` `log` などはここにあります。

```ruby
f = DFloat.linspace(-20,20,11)
# [-20, -16, -12, -8, -4, 0, 4, 8, 12, 16, 20]

NMath.sin(f)
# [-0.912945, 0.287903, 0.536573, -0.989358, 0.756802, 0, -0.756802, ...]

NMath.cos(f)
# [-0.912945, 0.287903, 0.536573, -0.989358, 0.756802, 0, -0.756802, ...]

NMath.tanh(f)
# [-1, -1, -1, -1, -0.999329, 0, 0.999329, 1, 1, 1, 1]
 
NMath.log(f)
# [nan, nan, nan, nan, nan, -inf, 1.38629, 2.07944, 2.48491, 2.77259, ...]

NMath.log10(f)
# [nan, nan, nan, nan, nan, -inf, 0.60206, 0.90309, 1.07918, 1.20412, ...]

NMath.sqrt(f)
# [-nan, -nan, -nan, -nan, -nan, 0, 2, 2.82843, 3.4641, 4, 4.47214]
```

## 要素を呼び出す
```ruby
a = Int32.new(10,10).seq
# [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 
#  [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], 
#  [20, 21, 22, 23, 24, 25, 26, 27, 28, 29], 
#  [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], 
#  [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], 
#  [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], 
#  [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], 
#  [70, 71, 72, 73, 74, 75, 76, 77, 78, 79], 
#  [80, 81, 82, 83, 84, 85, 86, 87, 88, 89], 
#  [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]
 
a[0,true]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
a[true, 0]
# [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
 
a[1..3, 2..4]
# [[12, 13, 14], 
#  [22, 23, 24], 
#  [32, 33, 34]]
 
a[1]
# 1
 
a[-1]
# 99
 
a.diagonal
# [0, 11, 22, 33, 44, 55, 66, 77, 88, 99]
 
a.transpose
# [[0, 10, 20, 30, 40, 50, 60, 70, 80, 90], 
#  [1, 11, 21, 31, 41, 51, 61, 71, 81, 91], 
#  [2, 12, 22, 32, 42, 52, 62, 72, 82, 92], 
#  [3, 13, 23, 33, 43, 53, 63, 73, 83, 93], 
#  [4, 14, 24, 34, 44, 54, 64, 74, 84, 94], 
#  [5, 15, 25, 35, 45, 55, 65, 75, 85, 95], 
#  [6, 16, 26, 36, 46, 56, 66, 76, 86, 96], 
#  [7, 17, 27, 37, 47, 57, 67, 77, 87, 97], 
#  [8, 18, 28, 38, 48, 58, 68, 78, 88, 98], 
#  [9, 19, 29, 39, 49, 59, 69, 79, 89, 99]]
 
a.reverse
# [[99, 98, 97, 96, 95, 94, 93, 92, 91, 90], 
#  [89, 88, 87, 86, 85, 84, 83, 82, 81, 80], 
#  [79, 78, 77, 76, 75, 74, 73, 72, 71, 70], 
#  [69, 68, 67, 66, 65, 64, 63, 62, 61, 60], 
#  [59, 58, 57, 56, 55, 54, 53, 52, 51, 50], 
#  [49, 48, 47, 46, 45, 44, 43, 42, 41, 40], 
#  [39, 38, 37, 36, 35, 34, 33, 32, 31, 30], 
#  [29, 28, 27, 26, 25, 24, 23, 22, 21, 20], 
#  [19, 18, 17, 16, 15, 14, 13, 12, 11, 10], 
#  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]]
 
a.reshape(4,25)
# [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...], 
#  [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, ...], 
#  [50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, ...], 
#  [75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, ...]
 
a.max
# 99
 
a.min
# 0
 
a.minmax
# [0, 99]
 
a.sum
# 4950
 
a.sum 0 # 軸の方向を指定
# [450, 460, 470, 480, 490, 500, 510, 520, 530, 540]
 
a > 49
# => Numo::Bit#shape=[10,10]
# [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
#  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
#  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
#  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
#  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
 
bit = (a > 49)
bit.where
# => Numo::Int32#shape=[50]
# [50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, ...]
# インデックスを返す
 
a.eq 33
# [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
 
(a.eq 33).where
# => Numo::Int32#shape=[1]
# [33]
 
a[a > 90]
# [91, 92, 93, 94, 95, 96, 97, 98, 99]
```

```ruby
a = Int32.new(10).seq - 5
# [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
 
# 絶対値
a.abs
# a = Int32.new(10).seq - 5
# [5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
 
b = Int32.new(10).seq
 
# 足しあわせ
b.cumsum
# [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
```

## 乱数
```ruby
# 一様な乱数
x = DFloat.new(1000).rand
y = DFloat.new(1000).rand
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/d723b401-a8e6-4f91-1c2e-d94d6177f4d1.png)

```ruby
# 正規分布
x = DFloat.new(1000).rand_norm
y = DFloat.new(1000).rand_norm
# rand_norm([mu,[sigma]]) 
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/d0635052-13b7-4aab-3275-2b937134ed43.png)

乱数にこだわりたい人はGNU Scientific Libraryをみると、面白い乱数がたくさん搭載されていたりします。
それらをNumo::GSLから呼び出してもよいかもしれません。

## 統計

```ruby
a = DFloat.new(100,100).rand_norm(1, 2)
# => Numo::DFloat#shape=[100,100]
 
# 要素数
a.size
# => 10000
 
# 平均
a.mean
# => 0.9941970100670163
 
# 中央値
a.median
# => Numo::DFloat#shape=[]
# 1.0030267444162986
 
# 分散
a.var
# => 3.9539947182922974
 
# 標準偏差
a.stddev
# => 1.9884654179271757
 
# 二乗平均平方根
a.rms
# => 2.223067028599605
```

## ソート

```ruby
na = Int32[1, 10, 2, 5, 9, 8, 12, 11, 3, 7, 4, 6]
 
na.sort
 
#=> Numo::Int32#shape=[12]
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
 
na.max_index
# => 6
 
na.min_index
# => 0

na.sort_index
=> Numo::Int32#shape=[12]
[0, 2, 8, 10, 3, 11, 9, 5, 4, 1, 7, 6]
```

## View
NArrayをスライシングした時に、ビューが返ってきます。

```ruby
a = Int8[1,2,3,4,5]
# [1, 2, 3, 4, 5]
b = a.view
# => Numo::Int8(view)#shape=[5]
# [1, 2, 3, 4, 5]
 
a[0] = 0
b
# => Numo::Int8(view)#shape=[5]
# [0, 2, 3, 4, 5]
 
b[1] = 0
a
# => Numo::Int8#shape=[5]
# [0, 0, 3, 4, 5]
```

## 結合

```ruby
a = Int32[1,2,3]
b = Int32[4,5,6]

a.append b
# [1,2,3,4,5,6]

a.concatenate b
# [1,2,3,4,5,6]

a.hstack b

Int32.hstack [a,b]
# [1,2,3,4,5,6]

Int32.dstack [b,b]
# => Numo::Int32#shape=[2,3,2]
# [[[1, 1], 
#  [2, 2], 
#  [3, 3]], 
# [[4, 4], 
#  [5, 5], 
#  [6, 6]]]
```

## 保存と読み込み
Marshal.dumpが使えます

```ruby
a = Int32.new(2,2).seq

# 保存
s = Marshal.dump(a)
File.binwrite("data", s)

# 読み込み
b = Marshal.load(File.read("data"))
a == b
# true
```

## インスペクトの表示サイズ調節

`inspect` によってターミナルの画面上に表示される画面のサイズを、次のメソッドで調節することができます。

```ruby
NArray.inspect_cols # 80 デフォルトの幅（文字数）
NArray.inspect_rows # 20 デフォルトの高さ（行数）
```

```ruby
NArray.inspect_cols = 20
NArray.inspect_rows = 5

Int32.new(100,100).seq

# => コンパクトに表示される
# Numo::Int32#shape=[100,100]
# [[0, 1, 2, 3, ...], 
#  [100, 101, ...], 
#  [200, 201, ...], 
#  [300, 301, ...], 
#  [400, 401, ...], 
 ...
```

# NArrayで高速計算するために

1. ループを使わない
    * Rubyを使っていると `map` などのイテレータをよく使います。しかし、NArrayではループを使わずに行列のまま計算します。
2. 次元の入れ替え
    * ちょっと次元を入れ替えるといろんな計算ができることがあります。
    * たとえば：[RubyでMnistを表示しつつ、４次元の不思議さを感じる](https://qiita.com/kojix2/items/ebcb35ddd7654b004f58)
    * しかし、これは数学が苦手な人（私）にはちょっと難しいです。
3. 条件分岐は `a[a>0]` `a[a.eq 0]` などBooleanマスクで代用する。
    * 配列のなかの特定の要素にのみ演算を加えたい場合でもifを使わないですみます。
4. `concatenate` `append` `hstack` `vstack` `hstack` などを避ける
    * 上記のメソッドはNArrayのサイズを変更するメソッドです。
    * とっても便利なのですが、実はRubyのコードで新しいNArrayを生成しています。
    * Cumoを併用する場合は避けた方が無難かもしれません。(Cumoが遅くなる原因になる)
    * 逆にCumoを使用する予定が全くないなら、あまり気にしなくてよいかも。
5. `inplace` を使う
    * `a = a + 10` と書くところを、`a.inplace + 10` と記載すると少し速くなります。
    * 上書きするのでメモリ確保の時間がはぶけるとのこと
6. Numo::Linalg や Cumo を導入する
    * NArrayで完結する行列計算を大量に行う場合、Cumoを導入するとかなりの高速化が期待できます。
    * CUDA環境を作るのは初心者にはハードルが高いです。十分に時間があるときに2台以上PCが使える状況で挑戦した方がよいでしょう。

# プロジェクトにフィードバックしよう

* 明らかに計算結果がおかしい場合は、単純にバグを踏んでる可能性もあります。そういうときはGitHubのページでプロジェクトにフィードバックしましょう。ほかにも困ってるユーザーがいるかも。
* 感想をブログなどに書いておいてもらえると、他のユーザーの参考になると思います。

# 付録 Numoファミリーについて

* NArray以外のプロジェクト
    * [Numo::Gnuplot](https://github.com/ruby-numo/numo-gnuplot)
    * [Numo::Linalg](https://github.com/ruby-numo/numo-linalg)
    * [Numo::GSL](https://github.com/ruby-numo/numo-gsl)
    * [Numo::FFTW](https://github.com/ruby-numo/numo-fftw)
    * [Numo::FFTE](https://github.com/ruby-numo/numo-ffte)

代替がないことも多く重宝する。


この記事は以上です。（この記事は自ブログの記事を改訂したものです。）

[^1]: 自分が知っている範囲とも