-
Notifications
You must be signed in to change notification settings - Fork 42
Numo::NArray Overview (Japanese)
このドキュメントはチュートリアルではなく、旧NArrayやnumpy等の数値配列システムを 知っている人向けに、新NArrayの特徴を述べたものです。
旧版の NArray では、要素の型をオブジェクトの属性としていました。 Ruby/Numo 版の NArray では、Numo::NArray クラスのサブクラスによって 要素の型を表すという仕様にしています。 例えば、Numo::DFloat クラスは、64ビット浮動小数点数の NArray クラスとなります。
-
整数
Numo::Int8 Numo::Int16 Numo::Int32 Numo::Int64 Numo::UInt8 Numo::UInt16 Numo::UInt32 Numo::UInt64
-
浮動小数点数
Numo::SFloat Numo::DFloat
-
浮動小数点複素数
Numo::SComplex Numo::DComplex
-
ビット
Numo::Bit
-
Rubyオブジェクト
Numo::RObject
-
構造体
Numo::Struct - 名称変更予定?
要素の型を属性でなくクラスにした理由は次の通りです。
- 「型を表すもの」が、旧NArrayは内部では整数であり、型との対応が明白ではなかったが、 新NArrayではRuby内で一意のクラスであり、型との対応が明白。
- 旧NArrayでは、型によって異なる演算関数を呼ぶために、メソッド1つ1つに関数マップが必要であったが、 新NArrayでは、Rubyのメソッドディスパッチシステムにより型ごとに処理が分岐されるため、関数マップが不要になる。
- 旧NArrayでは後から型を追加することが不可能だが、新NArrayでは可能である。
NArray のサブクラスの new
メソッドにより、配列のメモリが割り当てられていない
NArray オブジェクトを生成します。
$ irb -r numo/narray
irb> a = Numo::DFloat.new(4,5,6)
=> Numo::DFloat#shape=[4,5,6](empty)
irb> a.shape
=> [4, 5, 6]
irb> a.ndim
=> 3
irb> a.size
=> 120
new
メソッドの引数には、shape を指定します。
shape とは、多次元配列における各次元のサイズの配列です。
上の例(a.shape == [4,5,6]
)では、
3次元配列(a.ndim == 3
)を表し、
各次元の要素数はそれぞれ 4,5,6 であり、
合計の要素数は a.size == 4*5*6 == 120
となります。
new
で生成されたばかりの NArray に対して、要素にアクセスしようとすると例外が上がります。
次のメソッドにより、配列データのメモリを割り当て、値を格納します。
store(another_array)
fill(item)
seq([begin, step]), indgen
logseq(begin, step [,base])
eye([element, offset])
rand([[low,] high])
irb> Numo::DFloat.new(2,4,6).seq
=> Numo::DFloat#shape=[2,4,6]
[[[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]]]
初めからデータを持つ NArray オブジェクトを生成する方法として、 次の例に示すメソッドがあります。
irb> Numo::DFloat[1,2,3,5,7,11]
=> Numo::DFloat#shape=[6]
[1, 2, 3, 5, 7, 11]
irb> Numo::DFloat[1..100]
=> Numo::DFloat#shape=[100]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, ...]
irb> Numo::DFloat.cast(Numo::Int32[1,2,3,5,7,11])
=> Numo::DFloat#shape=[6]
[1, 2, 3, 5, 7, 11]
irb> Numo::DFloat.zeros(120)
=> Numo::DFloat#shape=[120]
[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, ...]
irb> Numo::DFloat.ones(2,4,6)
=> Numo::DFloat#shape=[2,4,6]
[[[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]]]
irb> Numo::DFloat.linspace(-5,5,7)
=> Numo::DFloat#shape=[7]
[-5, -3.33333, -1.66667, 0, 1.66667, 3.33333, 5]
irb> Numo::DFloat.logspace(4,0,5,2)
=> Numo::DFloat#shape=[5]
[16, 8, 4, 2, 1]
Ruby の Array クラスと同じように、
[]
メソッドに要素を参照、[]=
メソッドで要素を格納できます。
インデックスが負のときは、(その次元の)要素数を加えた値がインデックスとみなされます。
つまり最後の要素を参照するには -1 を指定します。
多次元配列では、各次元ごとにインデックスをカンマで区切って与えるか、 1次元のフラットな配列とみなした場合のインデックスを与えることもできます。
irb> a = Numo::DFloat.new(2,4,6).seq
=> Numo::DFloat#shape=[2,4,6]
[[[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]]]
irb> a[1,2,3]
=> 39.0
irb> a[39]
=> 39.0
Numo::NArray の次元の順序は、C-order、つまり後ろの方の次元が速く回ります。 (ちなみに旧版の NArray は Fortran-order)
[]
メソッドの引数に、
Range または Array を与えると、配列のビューを返します。
配列のビューとは、他の配列の一部または全部を参照する「窓」であり、
ビューに対して要素を操作すると、元の配列の要素も書き換えます。
irb> a = Numo::DFloat.new(6).seq
=> Numo::DFloat#shape=[6]
[0, 1, 2, 3, 4, 5]
irb> v = a[1..3]
=> Numo::DFloat(view)#shape=[3]
[1, 2, 3]
irb> v.store([11,12,13])
=> Numo::DFloat(view)#shape=[3]
[11, 12, 13]
irb> a
=> Numo::DFloat#shape=[6]
[0, 11, 12, 13, 4, 5]
# a[1..3] = [11,12,13] と同じ
(その次元の)すべての要素の参照(つまり0..-1
)は true で代替できます。
高度な使い方として、残りの次元に対してすべて true を与えたいが次元の個数が不定のとき、
false を与えることもできます。
インデックスが Array オブジェクトの場合は、Array をインデックスの配列と見なし、 そのインデックスが指すビューを返します。
irb a = Numo::DFloat.new(6).seq(1,0.5)
=> Numo::DFloat#shape=[6]
[1, 1.5, 2, 2.5, 3, 3.5]
irb> a[[2,3,5]]
=> Numo::DFloat(view)#shape=[3]
[2, 2.5, 3.5]
[]
とほぼ同じメソッドに slice
があります。
インデックスが整数の場合の挙動が、[]
と slice
で異なります。
[]
の場合、インデックスに整数を与えると、その次元が取り除かれ、次元数が減ります。
(Range/Array で指定した次元は、結果要素数が 1 でも残ります。)
irb> a = Numo::DFloat.new(3,4).seq
=> Numo::DFloat#shape=[3,4]
[[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]]
irb> a[1..2,3]
=> Numo::DFloat(view)#shape=[2]
[7, 11]
一方、slice
の場合は、整数を指定した次元でも、要素数 1 の次元として残り、
次元数が変わりません。
irb> a = Numo::DFloat.new(3,4).seq
=> Numo::DFloat#shape=[3,4]
[[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]]
irb> a.slice(1..2,3)
=> Numo::DFloat(view)#shape=[2,1]
[[7],
[11]]
以下のメソッドは、配列の shape を変更したビューを返します。
flatten([dim0,dim1,...])
transpose([dim0,dim1,...])
expand_dims(dim)
diagonal(offset, [ax1,ax2])
以下のメソッドは、配列の shape を変更したコピーを返します。
reshape
NArray には以下の演算メソッドがあります。
+
-
*
/
%
divmod
**
-@
abs
NArray の演算は、基本的に要素ごと(element-wise)の演算です。 以下のルールがあります。
- Upcast
- Broadcast
- coerce
- inplace
a*b
などの演算で、 a
と b
で型が異なる場合に、結果の型を決めるルールを Upcast といいます。
Numo::NArray では、基本的には下に書かれた順に、右の方の型に揃えるというルールにしています。
Ruby Numeric -> Numo::Int,UInt -> Numo::Float -> Numo::Complex -> Numo::RObject
Intのサイズや単精度・倍精度で異なる場合は大きい方の型になります。 また、Ruby Float と、Numo::Int32 など整数 NArray との演算では、結果は Numo::DFloat になります。
Upcast ルールを決めているのは、各型クラスネームスペースの UPCAST 定数にアサインされた Hash データです。 例えば Numo::Int32::UPCAST の内容は次のようになります。
irb> Numo::Int32::UPCAST
=> {Array=>Numo::Int32, Fixnum=>Numo::Int32, Bignum=>Numo::Int32,
Float=>Numo::DFloat, Complex=>Numo::DComplex,
false=>Numo::Int32, Numo::DComplex=>Numo::DComplex,
Numo::SComplex=>Numo::SComplex, Numo::DFloat=>Numo::DFloat,
Numo::SFloat=>Numo::SFloat, Numo::Int64=>Numo::Int64,
Numo::Int32=>Numo::Int32, Numo::UInt64=>Numo::Int64}
ここに含まれない Int8 型の時は、Numo::Int8::UPCAST を見に行く、というようにして決まります。
2つの NArray があり、
a.shape == [2,3,1]
b.shape == [1,3,4]
のとき、
c = a*b
c.shape == [2,3,4]
となります。
つまり、Broadcast とは、 片方の配列のある次元の要素数が 1 であり、 もう一方の配列の対応する次元の要素数が 1 より大きいとき、 要素数1の要素が繰り返し使われるというルールです。 それ以外で次元ごとのの要素数が異なる場合は、例外が上がります。
一方の次元数が少ない場合は、次元が前側(遅く回る側)に拡張され、1 が入ったものとみなされます。 つまり、次の NArray の場合も、結果は上の例と同じになります。
a.shape == [2,3,1]
b.shape == [3,4]
NArray は coerce をサポートしています。したがって、
irb> a = Numo::DFloat.new(5).seq
=> Numo::DFloat#shape=[5]
[0, 1, 2, 3, 4]
irb> 0.5 * a
=> Numo::DFloat#shape=[5]
[0, 0.5, 1, 1.5, 2]
という書き方も可能です。coerce の中で、0.5 の Float が、Upcast で決まる型の NArray オブジェクトに変換され、改めて演算メソッドが呼ばれます。 このとき、0.5 のオブジェクトは 0 次元の NArray に変換されます。 したがって、前出の Broadcast ルールの適用によって、配列に対して値が繰り返し適用されます。
inplace メソッドは、inplace フラグが立ったビューを返します。 inplace フラグが立った NArray は、inplace に対応したメソッドが 呼ばれたとき、結果を元の配列に上書きします。
irb> a = Numo::DFloat.new(5).seq
=> Numo::DFloat#shape=[5]
[0, 1, 2, 3, 4]
irb> a.inplace + 1
=> Numo::DFloat#shape=[5]
[1, 2, 3, 4, 5]
irb> a
=> Numo::DFloat#shape=[5]
[1, 2, 3, 4, 5]
# a に上書きされている
inplace を使わない場合、次のように書いても同じ結果が得られます。
a += 1 # a = a + 1 のシンタックスシュガー
しかしこの場合は、演算結果の配列を新たに確保してからそこに書き込むことになります。 一方、inplace の場合は、結果の配列を確保せず、元の配列に上書きするため、 メモリの節約となり、速度も向上します。
NArray は、次の条件メソッドを持ちます。
eq
ne
gt (>)
ge (>=)
lt (<)
le (<=)
nearly_eq
isnan
isinf
isfinite
これらは Numo::Bit 配列を返します。 Numo::Bit 配列は、次のメソッドを持ちます。
and (&)
or (|)
xor (^)
not (~)
count_true
count_false
all?
any?
none?
where
条件式の使い方の例として、次のいずれかの方法で、負数を0に置き換えます。
irb> a = 3 - Numo::DFloat.new(7).seq
=> Numo::DFloat#shape=[7]
[3, 2, 1, 0, -1, -2, -3]
irb> a[a<0] = 0
=> 0
irb> a
=> Numo::DFloat#shape=[7]
[3, 2, 1, 0, 0, 0, 0]
irb> a = 3 - Numo::DFloat.new(7).seq
=> Numo::DFloat#shape=[7]
[3, 2, 1, 0, -1, -2, -3]
irb> a[(a<0).where] = 0
=> 0
irb> a
=> Numo::DFloat#shape=[7]
[3, 2, 1, 0, 0, 0, 0]
次の統計メソッドが定義されています。
sum
prod
mean
stddev
var
rms
min
min_index
max
max_index
minmax
cumsum
cumprod
sort
sort_index
median
引数に次元番号(左が0番目、複数個指定可)を与えると、 指定した次元の中で統計を求めます。
sqrt, exp, log, 三角関数などが定義されています。