16
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NArrayの簡単なつかい方

Last updated at Posted at 2019-04-24

RubyにはNumPyがないってよく言われますが、RubyにもNArrayがあるよ。
NArrayの速度をきちんと測定した資料は少ないですが、NumPyとほとんど同等の速度が出せるとされています。
   image.png     image.png

NArrayについて

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

  • Numo::NArray
    • Ruby言語における事実上のNumPy代替のスタンダード
    • 機械学習のRumaleやAnkaneさんのプロジェクトなどに多数採用された。
    • GPGPUに対応したCumoもある。
    • 単にNArrayといった場合、前身プロジェクトの旧NArrayを意味していることがある。

リファレンスおよび資料

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

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を行った状態で書きます。

require 'numo/narray'
include Numo

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

NArrayの生成

require 'numo/narray'
include Numo
Int32[1,2,3]

 [1, 2, 3]

Int32.new(3,3).seq

 [[0, 1, 2],
  [3, 4, 5],
  [6, 7, 8]]

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

 [[0, 1],
  [2, 3]]

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

 [[0, 1],
  [2, 3]]

Int32[1..5]

 [1, 2, 3, 4, 5]

Int32[1...5]

  [1, 2, 3, 4]

NArrayの四則演算

require 'numo/narray'
include Numo
a = Int32.new(3,3).seq

 [[0, 1, 2],
  [3, 4, 5],
  [6, 7, 8]]

b = Int32.new(3,3).seq + 1

 [[1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]]

a + b

 [[1, 3, 5],
  [7, 9, 11],
  [13, 15, 17]]

b - a

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

a * b

 [[0, 2, 6],
  [12, 20, 30],
  [42, 56, 72]]

a * 2

 [[0, 2, 4],
  [6, 8, 10],
  [12, 14, 16]]

a * 0.1

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

型を変換する

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

a = UInt8[1,2,3]
SFloat.cast(a)
# => Numo::SFloat#shape=[3]

とします。ここではUInt8をSFloatに変換している。

行列積

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

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を使います。

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

いろいろな行列を生成する

ゼロ行列

x = Int32.zeros(3,3)

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

全要素が1の行列

x = Int32.ones(3,3)

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

全要素が2の行列

z = y.fill 2

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

単位行列

y = Int32.eye(3,3)

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

等間隔の行列

f = DFloat.linspace(-20,20,11)

[-20, -16, -12, -8, -4, 0, 4, 8, 12, 16, 20]

NArrayの情報を取得する

e = DFloat.new(3,4).seq
 
# 各次元
e.shape              # => [3, 4]
 
# 要素の数
e.size               # => 12
 
# バイトサイズ
e.byte_size          # => 96
 
# 次元数
e.rank               # => 2 #e.ndim も同じ

文字列との相互変換

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

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で大丈夫です

a = UInt8[1,2,3]

a.to_a 

NMath

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

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]

要素を呼び出す

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]
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]

乱数

# 一様な乱数
x = DFloat.new(1000).rand
y = DFloat.new(1000).rand

image.png

# 正規分布
x = DFloat.new(1000).rand_norm
y = DFloat.new(1000).rand_norm
# rand_norm([mu,[sigma]]) 

image.png

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

統計

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

ソート

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をスライシングした時に、ビューが返ってきます。

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]

結合

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が使えます

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

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

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

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

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

NArray.inspect_cols # 80 デフォルトの幅(文字数)
NArray.inspect_rows # 20 デフォルトの高さ(行数)
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. 次元の入れ替え
  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ファミリーについて

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

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

  1. 自分が知っている範囲とも

16
13
0

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
16
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?