はじめに
ndarrayについてあまり記事がありませんでしたので,困った部分を残しておきます.
Nalgebraとの比較については,拙著
[Rust] Nalgebra入門(ndarrayとの比較付き)
をご覧ください.
別のcgmathというクレートも次元を固定して扱えるので,3〜4次元程度しか扱わなければ便利そうですね.クォータニオンも扱えるらしいですし.
ndarrayは,NumPyのnpyファイルを扱えるようになるクレートや画像ファイルからの変換クレートなど,いろいろなクレートがあって便利ですね!
ndarrayをNumPyと比較しながら入門するにはndarray::doc::ndarray_for_numpy_usersが一番だと思いますが,それでは困ったことが多かったので...
ndarrayで線形代数を扱うためのクレート ndarray-linalgについては,Rustで線形代数 (ndarray-linalgの使い方)が詳しいです.
余談ですが、ndarrayはnum系のクレートに依存しているので、numとの相性はよさそうです.
Arrayの初期化
自分でも書こうかと思いましたが,
[Rust] ndarray で行列を定義する方法一覧で書かれているので十二分だと思います(ありがとうございます).
結局array!マクロを使うのが一番な気がします.
それから,Array::from_vec()がduplicatedになっているので,Array::from()かVecのinto()メソッドを使いましょう.
要素へのアクセス
a[[1, 2]]とか,a[(1, 2)]と配列でもタプルでもできますが,ndarray::doc::ndarray_for_numpy_usersでは[[1, 2]]なので,配列を与えた方がいいのでしょうか.
(片方に統一してほしい.両方あるのは,一時Rustで[(1, 2)]を[1, 2]で表していいよ見たいな話があったからでしょうか.わからない)
shape
shape()メソッドで&[usize]で得られます.
1次元配列の長さ
len() で取得できます.ドキュメントを探すのに苦労しました.2Dは書いてあるのに,1Dは特別なメソッドがないからという理由で埋もれていました...
2次元配列のshape
nrows(), ncols()です
Vec to Array
Array::from_shape_vec(shape, v)が便利です
shapeには(usize, usize)などのタプルを渡せばOKです.
let arr = Array::from_shape_vec((2, 3), vec![0, 1, 2, 3, 4, 5]).unwrap();
assert_eq!(arr, array![[0, 1, 2],
[3, 4, 5]]);
slice
s!マクロ(https://docs.rs/ndarray/0.14.0/ndarray/macro.s.html)
を,slice,slice_mut,slice_moveメソッドに適用することでできます.NumPyのようにarr[1:3:-1]のようなスライスもarr.slice(s![1..3;-1])とすることで扱えます.
let arr = array![0, 1, 2, 3];
assert_eq!(arr.slice(s![1..3;-1]), array![2, 1]);
assert_eq!(arr.slice(s![1..;-2]), array![3, 1]);
assert_eq!(arr.slice(s![0..4;-2]), array![3, 1]);
assert_eq!(arr.slice(s![0..;-2]), array![3, 1]);
assert_eq!(arr.slice(s![..;-2]), array![3, 1]);
Array to ArrayView / ArrayViewMut
ArrayViewというのはArrayを複製せずに値を参照したいときに使う型で,&Arrayより便利です.また,sliceの返り値でもあります.
それのmut版がArrayViewMutです.&mut Arrayより便利です.これはslice_mutの返り値です.
これらはCopyトレイトを実装しています(参照がコピーされるだけで実体はコピーされません).
ArrayからArrayView, ArrayViewMutを呼ぶにはview(),view_mut()メソッドを使います.例えば
let mut arr = array![[1, 2, 3], [4, 5, 6]];
some_func(arr.view()); // some_func(av: ArrayView<_>)
mut_func(arr.view_mut()); // mut_func(mut av: ArrayViewMut<_>)
とします.より具体的には
fn main() {
let mut arr = array![[1, 2], [3, 4]];
mut_arr(arr.view_mut());
assert_eq!(arr, array![[0, 2], [3, 4]]);
}
fn mut_arr(mut av: ArrayViewMut2<i32>) {
av[[0, 0]] = 0;
}
です.動作はplay groundで確かめられます.
ArrayView / ArrayViewMut to Array
逆にArrayView,ArrayViewMutをArrayに直すときは
to_owned()を使います.同じようなinto_owned()もありますが,Rust標準のto_owned()がいいでしょう.
ArrayViewMutに行ベクトル/列ベクトルを代入する
ndarrayのスライスはb.slice_mut(s![.., 0])(型はArrayViewMut<_>)の形となるため,NumPyのように
a[:, 0] = fooのように代入することができません.そこでassignメソッドを使います.
b.slice_mut(s![i, ..]).assign(&array![0, 1, 2]]); // b[i, :] = np.array([0, 1, 2])
具体的には,列ベクトル$b_1,\cdots,b_n$について
$$
\mathtt{swap}([b_1,\cdots, b_n],\ i,\ k)=[b_1,\cdots b_{i-1},b_k,b_{i},\cdots,b_{k-1}, b_{k+1},\cdots,b_n]
$$
のようなケースの場合,以下のようにやります.
fn swap(mut b: ArrayViewMut2<i32>, i: usize, k: usize) {
let row_i = b.slice(s![.., i]).to_owned();
let tmp = b.slice(s![.., k]).to_owned();
b.slice_mut(s![.., i]).assign(&tmp);
for l in ((i + 1)..k).rev() {
let tmp = b.slice(s![.., l]).to_owned();
b.slice_mut(s![.., l + 1]).assign(&tmp);
}
b.slice_mut(s![.., i + 1]).assign(&row_i);
}
map, mapvについて
arr.map(|a| *a > 0);
arr.map(|a: &i32| *a > 0); // 型を明示
arr.mapv(|a| a > 0);
arr.mapv(|a: i32| a > 0); // 型を明示
という感じです.mapは要素の参照(&i32など),mapvは要素そのもの(i32など)を引数に取る関数を適用します.
要素ごとに関数を適用するような場合は
a.mapv(|a| a.powi(3)); // a ** 3 (NumPy)
a.mapv(f64::sqrt); // np.sqrt(a) (NumPy)
とします.
演算子について
NumPy同様で,a * b,a * &b,&a * &bなどは要素ごとの積を表します.行列積はa.dot(&b)を使います.
NumPyのreshape
NumPyのreshapeはinto_shapeです.
use ndarray::{aview1, aview2};
assert!(
aview1(&[1., 2., 3., 4.]).into_shape((2, 2)).unwrap()
== aview2(&[[1., 2.],
[3., 4.]])
);
転置
転置はarr.t()です.
逆行列
ndarray-linalgクレートを使います.
このクレートを使うときは,crates.ioにあるとおり,backendを指定するようにしましょう.
[dependencies]
ndarray = "0.14"
ndarray-linalg = { version = "0.13", features = ["openblas-static"] }
逆行列を計算するには,
use ndarray_linalg::solve::Inverse;
let inv_arr = Inverse::inv(&arr);
とします。Resultにwrapされているので,よしなにエラー処理してください.
inv()メソッドをArrayに生やすこともできそうですが,やり方がわかりませんでしたので,どなたか教えてください...
なぜかndarray-linalgを混ぜて使うと、dotメソッドが複数あると言われてしまうので
bf_arr.dot(&Inverse::inv(&af_arr).unwrap())
が使えないことがあり、
Array2::<f64>::dot(&bf_arr, &Inverse::inv(&af_arr).unwrap())
のようにする必要がありました.うーん...
(このせいでcgmathに浮気しそうになった)
おまけ
RustでDeepLearning入門
ここでは紹介しきれなかった,ndarrayの具体的な場合の扱い方が書いてあるのでおすすめです.
それから
[多次元配列、線形代数 (ndarray, ndarray-linalg crate)]
(https://zenn.dev/termoshtt/books/b4bce1b9ea5e6853cb07/viewer/ndarray_linalg)
よさそうな記事がZennに出ていたので.
SEOが良くなるともっと上に出てきそうですね!