覚え書きブログ

ブロック対角行列

ブロック対角行列は、numpyのkronとeyeを用いて作ることができる。

例えば、以下の6行3列の行列zの2行ずつ取り出して対角に並べてみる。

 z=np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]])
> z
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12],
       [13, 14, 15],
       [16, 17, 18]]
> [np.kron(np.eye(3)[i:i+1],z[i:i+1,:]) for i in range(3)]
[array([[1., 2., 3., 0., 0., 0., 0., 0., 0.]]), array([[0., 0., 0., 4., 5., 6., 0., 0., 0.]]), array([[0., 0., 0., 0., 0., 0., 7., 8., 9.]])]
>np.vstack([np.kron(np.eye(3)[i:i+1],z[i:i+1,:]) for i in range(3)])
array([[1., 2., 3., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 4., 5., 6., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 7., 8., 9.]])

リスト内包表記を用いない方法はあるのだろうか?

ブロック単位のreshape その2

hirotaka-hachiya.hatenablog.com
前回のブロック単位でのreshapeはある特定の行列の形にしか対応できなかったが、以下のサイトによるとnumpy.hsplitとnumpy.vstackを組み合わせるとどんな行列にも対応できそう。
stackoverflow.com

以下のように、4行6列の行列をnumpy.hsplitで行方向に3つに分割し、そのあとnumpy.vstackを用いて縦方向に繋げる。

> z=np.arange(24).reshape(4,6)
> z
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
> np.hsplit(z,3)
[array([[ 0,  1],
       [ 6,  7],
       [12, 13],
       [18, 19]]), array([[ 2,  3],
       [ 8,  9],
       [14, 15],
       [20, 21]]), array([[ 4,  5],
       [10, 11],
       [16, 17],
       [22, 23]])]
> np.vstack(np.hsplit(z,3))
array([[ 0,  1],
       [ 6,  7],
       [12, 13],
       [18, 19],
       [ 2,  3],
       [ 8,  9],
       [14, 15],
       [20, 21],
       [ 4,  5],
       [10, 11],
       [16, 17],
       [22, 23]])

そうすると、2行2列のブロック単位で縦に並べることができる。例えば、[[0,1],[6,7]]や[[2,3],[8,9]]がブロックである。
しかしながら、[[0,1],[6,7]]の後に本来は[[2,3],[8,9]]が来てほしいところ、元々下にある[[12,13],[18,19]]が来てしまっていて、ブロックの順番が期待通りにはなっていなかった。

そこで、今度は、numpy.vsplitを用いて縦方向に2つに分割してから、各固まりをnumpy.hsplitを用いてそれぞれ横方向に3つに分割してから、numpy.vstackで縦方向に並べてみた。
リスト内包表記が入り、少し複雑にはなるが以下のようになる。

>np.vstack([np.hsplit(np.vsplit(z,2)[i],3) for i in range(2)]).reshape(12,2)
array([[ 0,  1],
       [ 6,  7],
       [ 2,  3],
       [ 8,  9],
       [ 4,  5],
       [10, 11],
       [12, 13],
       [18, 19],
       [14, 15],
       [20, 21],
       [16, 17],
       [22, 23]])

リスト内包表記なしでもnumpy.hstackを使えばおなじことができた。

>np.vstack(np.hsplit(np.hstack(np.vsplit(z,2)),6))
array([[ 0,  1],
       [ 6,  7],
       [ 2,  3],
       [ 8,  9],
       [ 4,  5],
       [10, 11],
       [12, 13],
       [18, 19],
       [14, 15],
       [20, 21],
       [16, 17],
       [22, 23]])

同じ原理で、tensorflowでもsplitとconcatを用いてブロック単位でのreshapeができる。

> z=tf.constant(z)
> z
<tf.Tensor: shape=(4, 6), dtype=int32, numpy=
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])>
> tf.concat(tf.split(tf.concat(tf.split(z,2,axis=0),axis=1),6,axis=1),axis=0)
<tf.Tensor: shape=(12, 2), dtype=int32, numpy=
array([[ 0,  1],
       [ 6,  7],
       [ 2,  3],
       [ 8,  9],
       [ 4,  5],
       [10, 11],
       [12, 13],
       [18, 19],
       [14, 15],
       [20, 21],
       [16, 17],
       [22, 23]])>

ブロック単位のreshape

以下の、6行4列の行列を2行2列を一塊とするブロック単位でreshapeして12行2列の行列に変換するとする。

z=np.arange(24).reshape(6,4)
>z
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19],
       [20, 21, 22, 23]])

その場合、普通にreshapeを使うと以下のように、2行2列のブロックはおかまないなしで変換される。
つまり、最初は[[0,1],[4,5]]というブロックだったのに対し、変換後は[[0,1],[2,3]]となってしまっている。

>np.reshape(z,[12,2])
array([[ 0,  1],
       [ 2,  3],
       [ 4,  5],
       [ 6,  7],
       [ 8,  9],
       [10, 11],
       [12, 13],
       [14, 15],
       [16, 17],
       [18, 19],
       [20, 21],
       [22, 23]])

6行4列の行列は、2行2列のブロックが3行2列あるわけなので、expand_dimsを2回作り(1, 1, 6, 4)にした後、
(3, 2, 2, 2)にreshapeする。この最初の(3,2)がブロックの配列で、(2,2)がブロックの大きさに対応している。

> np.expand_dims(np.expand_dims(z,0),0).shape 
(1, 1, 6, 4)
>np.reshape(np.expand_dims(np.expand_dims(z,0),0),[3,2,2,2]).shape
(3, 2, 2, 2)
>zz = np.reshape(np.expand_dims(np.expand_dims(z,0),0),[3,2,2,2])
>zz
array([[[[ 0,  1],
         [ 2,  3]],

        [[ 4,  5],
         [ 6,  7]]],


       [[[ 8,  9],
         [10, 11]],

        [[12, 13],
         [14, 15]]],


       [[[16, 17],
         [18, 19]],

        [[20, 21],
         [22, 23]]]])

しかしながら、これも単にreshapeしただけなので、最初のブロックは[[0,1], [4,5]]となってほしいところが[[0,1],[2,3]]になっている。
ここで、[0,1]は4次元配列で言うと、その番地は(0,0,0)に対応し、[2,3]は(0,0,1)、[4,5]は(0,1,0)、[6,7]は(0,1,1)に対応している。
つまり、現在ブロックとしてくっついてしまっている[[0,1],[2,3]]は番地で言うと、(0,0,0)と(0,0,1)に、ブロックでくっつけてほしい[[0,1],[2,3]]は番地で言うと(0,0,0)と(0,0,1)に対応している。つまり、1軸目と2軸目の番地を入れ替えると、[0,1]は(0,0,0)のままであるのに対し、[2,3]は(0,1,0), [4,5]は(0,0,1)となるので、[4,5]は[0,1]とくっつけることができる。

他はどうだろうか?次のブロックは[[2,3],[6,7]]だが、1軸目と2軸目の番地を入れ替えると、[2,3]は(0,1,0)、[6,7]は(0,1,1)となる。したがって、同じブロックとしてくっつけることができている。

したがって、以下のようにtransposeを用いて入れ替えると以下のようになる。

> np.transpose(zz,[0,2,1,3])
array([[[[ 0,  1],
         [ 4,  5]],

        [[ 2,  3],
         [ 6,  7]]],


       [[[ 8,  9],
         [12, 13]],

        [[10, 11],
         [14, 15]]],


       [[[16, 17],
         [20, 21]],

        [[18, 19],
         [22, 23]]]])

>np.transpose(zz,[0,2,1,3]).reshape(12,2)
array([[ 0,  1],
       [ 4,  5],
       [ 2,  3],
       [ 6,  7],
       [ 8,  9],
       [12, 13],
       [10, 11],
       [14, 15],
       [16, 17],
       [20, 21],
       [18, 19],
       [22, 23]])

しかし、この方法では、ブロックが2行2列の場合、元々の行列の列数が4の場合しかうまくいかない。
例えば、以下のように4行6列の場合はうまくいかない。

>z=np.arange(24).reshape(4,6)
>z
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])
> zz = np.reshape(np.expand_dims(np.expand_dims(z,0),0),[2,3,2,2])
> zz
array([[[[ 0,  1],
         [ 2,  3]],

        [[ 4,  5],
         [ 6,  7]],

        [[ 8,  9],
         [10, 11]]],


       [[[12, 13],
         [14, 15]],

        [[16, 17],
         [18, 19]],

        [[20, 21],
         [22, 23]]]])
np.transpose(zz,[0,2,1,3]).reshape(12,2)
array([[ 0,  1],
       [ 4,  5],
       [ 8,  9],
       [ 2,  3],
       [ 6,  7],
       [10, 11],
       [12, 13],
       [16, 17],
       [20, 21],
       [14, 15],
       [18, 19],
       [22, 23]])

本当は、[[0,1],[6,7]]のブロックを作ってほしいのだが、[[0,1],[4,5]]のブロックになっている。残念。
他によい方法がないだろうか。。。