NumPyのstride_tricks.as_stridedの使用例


# はじめに

音声処理でよく使うフレームシフト処理（スライディングウィンドウとか，Pandasのrolling的な処理（？）とも呼ぶ）をしたい．
`numpy.lib.stride_tricks.as_strided` を使えばよいという情報にたどり着くも，思いのほかはまってしまったので，備忘録的にいくつかの例を書いてみた．

# 1次元のデータ

いろいろ調べてみたものの，2次元の例が多く自分にはイメージできなかったので，
最初は1次元から考えていく．1次元ならワタシチョトダケワカルので．．．

``` python
import numpy as np
from numpy.lib.stride_tricks import as_strided
```

まずは，適当に長さ10の`ndarray`を作る．そして，いくつかの情報を確認して，データ構造のイメージを持っておく．
特に，`itemsize`は，後でstrideのbyte数を考えるときに必要になる．

``` python
x = np.arange(10, dtype='float64')
# -> array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
x.size
# -> 10
x.shape
# -> (10,)
x.strides
# -> (8,)
x.itemsize
# -> 8
```

早速`as_stride`を試してみる．まずは，1つとばしにしてみる．
要素1つで8バイトなので，1つスキップするなら，こうだろうか？

``` python
as_strided(x, strides=(16,))
# -> array([0.00000000e+000, 2.00000000e+000, 4.00000000e+000, 6.00000000e+000,
#       8.00000000e+000, 0.00000000e+000, 4.80906177e-073, 4.63229019e-073,
#       3.74803829e-073, 4.16239459e+201])
```

末尾が怪しいが，，，C言語で配列外にアクセスしてしまった時が思い出される．
きちんとshapeも与えておくなら，こんな感じか．

``` python
as_strided(x, shape=(5,), strides=(16,))
# -> array([0., 2., 4., 6., 8.])
```

つぎは，スライディングウィンドウ処理の基本として，2つずつのデータとして取り出してみたい．こんな感じか．

``` python
as_strided(x, shape=(5,2))
# -> array([[0., 1.],
#        [2., 3.],
#        [4., 5.],
#        [6., 7.],
#        [8., 9.]])
```

できたけど，この時のstrideはどうなっているのだろうか？

``` python
as_strided(x, shape=(5,2)).strides
# -> (16, 8)
```

最初に調べた`x.stirdes`から自動で変わっている？ようだ．
1つ目の16は下側に見ていく時に次のデータが存在する場所までのバイト数で，
2つ目の8は右側に見ていく時に次のデータが存在する場所までのバイト数である．
つまり，右には1つずつ見ていけばよく，下には1つとばして2つ目をみればよい，ということだ．
C言語の画像処理でよくやるアレに似てるな，と思ったし，これがイメージできれば怖くないのではないか．

``` c
int W = 16;
int H = 32;
unsigned char Image[W*H];

for(w=0; w<W; w++) for(h=0; h<H; h++) {
	Image[h*W + w] = ...;
}
```

さて，次はオーバーラップを考慮しよう．下に見るときに2つ隣ではなく少し手前を見に行けばよい．
ということは，

``` python
as_strided(x, shape=(9,2), strides=(8,8))
# -> array([[0., 1.],
#           [1., 2.],
#           [2., 3.],
#           [3., 4.],
#           [4., 5.],
#           [5., 6.],
#           [6., 7.],
#           [7., 8.],
#           [8., 9.]])
```

はみ出したらどうなるだろうか？

``` python
as_strided(x, shape=(10,2), strides=(8,8))
# -> array([[0., 1.],
#           [1., 2.],
#           [2., 3.],
#           [3., 4.],
#           [4., 5.],
#           [5., 6.],
#           [6., 7.],
#           [7., 8.],
#           [8., 9.],
#           [9., 0.]])
```

ゼロ詰めかな・・・？もう少し違う形にしてみよう．3つずつの塊で，2つずらして読む形だとどうなるか．

``` python
as_strided(x, shape=(5,3), strides=(16,8))
# -> array([[0., 1., 2.],
#           [2., 3., 4.],
#           [4., 5., 6.],
#           [6., 7., 8.],
#           [8., 9., 0.]])
```

# 2次元のデータ

次に，2次元の特徴量の配列から，スライディングウィンドウ処理をしたい時を考えておきたい．

``` python
X = np.arange(10, dtype='float64').reshape(5,2)
# X -> array([[0., 1.],
#             [2., 3.],
#             [4., 5.],
#             [6., 7.],
#             [8., 9.]])
```

3次元のデータにすることを考えるとして，元の構造(2次元の特徴量)をキープするなら，stridesの最後の2つの数字はそのままにすればいい．つまり，元のfloat64で8 byteを指定，それを2つ組にしているので16 byteを指定．
あとは，その2つ組を1つずつずらしたいなら 1 x 16 =16 byte, 2つずつずらしていきたいなら 2 x 16 = 32 bytes となる．

``` python
as_strided(X, shape=(4,2,2), strides=(16,16,8))
# -> array([[[0., 1.],
#            [2., 3.]],
#    
#           [[2., 3.],
#            [4., 5.]],
#    
#           [[4., 5.],
#            [6., 7.]],
#    
#           [[6., 7.],
#            [8., 9.]]])

as_strided(X, shape=(3,2,2), strides=(32,16,8))
# -> array([[[ 0.00000000e+000,  1.00000000e+000],
#            [ 2.00000000e+000,  3.00000000e+000]],
#    
#           [[ 4.00000000e+000,  5.00000000e+000],
#            [ 6.00000000e+000,  7.00000000e+000]],
#    
#           [[ 8.00000000e+000,  9.00000000e+000],
#            [ 0.00000000e+000, -1.29986726e-231]]])
```

shapeを変えれば，2次元特徴量を3つ分見る，みたいなことも可能．

``` python
as_strided(X, shape=(4,3,2), strides=(16,16,8))
# -> array([[[ 0.00000000e+000,  1.00000000e+000],
#            [ 2.00000000e+000,  3.00000000e+000],
#            [ 4.00000000e+000,  5.00000000e+000]],
#    
#           [[ 2.00000000e+000,  3.00000000e+000],
#            [ 4.00000000e+000,  5.00000000e+000],
#            [ 6.00000000e+000,  7.00000000e+000]],
#    
#           [[ 4.00000000e+000,  5.00000000e+000],
#            [ 6.00000000e+000,  7.00000000e+000],
#            [ 8.00000000e+000,  9.00000000e+000]],
#    
#           [[ 6.00000000e+000,  7.00000000e+000],
#            [ 8.00000000e+000,  9.00000000e+000],
#            [ 0.00000000e+000, -1.29986726e-231]]])
```

なんとなく使い方がわかった．だが，shapeの計算＆ゼロ詰めは自分でしたほうがよいかもしれない．

また，繰り返しになるがstridesはバイト数である．自分が扱ってるデータのバイト数がわからないならitemsizeなどで確認すればよい．（C言語の sizeof 的な感じ．）