ではnumpy
、一部の演算は の形状を返します(R, 1)
が、一部の演算は を返します(R,)
。明示的なreshape
が必要になるため、行列の乗算がより面倒になります。たとえば、行列 に対して、M
を実行する場合、は行数です (もちろん、列方向にも同じ問題が発生します)。は の形状ですがは の形状であるため、エラーが発生します。numpy.dot(M[:,0], numpy.ones((1, R)))
R
matrices are not aligned
M[:,0]
(R,)
numpy.ones((1, R))
(1, R)
私の質問は次のとおりです:
(R, 1)
形状との違いは何ですか(R,)
。文字通り、数値のリストとリストのリストであり、すべてのリストに数値のみが含まれていることは知っています。行列の乗算を容易にするために、 ではなくnumpy
形状を優先するように設計しないのはなぜか疑問に思います。(R, 1)
(R,)
上記の例に対して、より良い方法はありますか? 明示的に形を変えずに、次のようにします。
numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))
ベストアンサー1
1. NumPyにおける図形の意味
あなたは「文字通り、これは数字のリストであり、すべてのリストに数字のみが含まれるリストのリストであることはわかっています」と書いていますが、それは少し役に立たない考え方です。
NumPy 配列について考える最も良い方法は、配列が単なる生の要素のブロックであるデータ バッファーと、データ バッファーの解釈方法を記述するビューの 2 つの部分で構成されていると考えることです。
たとえば、12 個の整数の配列を作成する場合:
>>> a = numpy.arange(12)
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
次に、a
次のように配置されたデータ バッファーで構成されます。
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
データの解釈方法を説明するビュー:
>>> a.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
>>> a.dtype
dtype('int64')
>>> a.itemsize
8
>>> a.strides
(8,)
>>> a.shape
(12,)
ここで、形状は (12,)
、配列が 0 から 11 までの単一のインデックスによってインデックス付けされることを意味します。概念的には、この単一のインデックスにラベルを付けるとi
、配列はa
次のようになります。
i= 0 1 2 3 4 5 6 7 8 9 10 11
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
もし私達形を変える配列の場合、データ バッファは変更されません。代わりに、データを解釈する別の方法を記述する新しいビューが作成されます。つまり、次のようになります。
>>> b = a.reshape((3, 4))
配列はb
と同じデータ バッファを持ちますが、今度は0 から 2 と 0 から 3 までの2 つのa
インデックスによってインデックス付けされます。2 つのインデックスにと というラベルを付けると、配列は次のようになります。i
j
b
i= 0 0 0 0 1 1 1 1 2 2 2 2
j= 0 1 2 3 0 1 2 3 0 1 2 3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
つまり、次のようになります。
>>> b[2,1]
9
2 番目のインデックスは急速に変化し、1 番目のインデックスはゆっくりと変化していることがわかります。これを逆にしたい場合は、パラメータを指定できますorder
。
>>> c = a.reshape((3, 4), order='F')
結果は次のようにインデックス付けされた配列になります。
i= 0 1 2 0 1 2 0 1 2 0 1 2
j= 0 0 0 1 1 1 2 2 2 3 3 3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
つまり、次のようになります。
>>> c[2,1]
5
これで、配列が 1 つ以上の次元のサイズ 1 を持つ形状を持つことが何を意味するかが明らかになったはずです。変更後:
>>> d = a.reshape((12, 1))
配列d
は 2 つのインデックスでインデックス付けされ、最初のインデックスは 0 から 11 までで、2 番目のインデックスは常に 0 です。
i= 0 1 2 3 4 5 6 7 8 9 10 11
j= 0 0 0 0 0 0 0 0 0 0 0 0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
など:
>>> d[10,0]
10
長さ 1 の次元は (ある意味で) 「自由」なので、自由に行動するのを妨げるものは何もありません。
>>> e = a.reshape((1, 2, 1, 6, 1))
次のようにインデックス付けされた配列を指定します。
i= 0 0 0 0 0 0 0 0 0 0 0 0
j= 0 0 0 0 0 0 1 1 1 1 1 1
k= 0 0 0 0 0 0 0 0 0 0 0 0
l= 0 1 2 3 4 5 0 1 2 3 4 5
m= 0 0 0 0 0 0 0 0 0 0 0 0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
など:
>>> e[0,1,0,0,0]
6
を参照してくださいNumPy 内部ドキュメント配列の実装方法の詳細については、こちらをご覧ください。
2. 何をすべきか?
以来numpy.reshape
新しいビューを作成するだけなので、必要なときにいつでも使用することを恐れる必要はありません。これは、配列を別の方法でインデックス付けしたい場合に使用する適切なツールです。
ただし、長い計算では、通常、最初から「正しい」形状の配列を構築するように調整して、形状変更と転置の数を最小限に抑えることができます。ただし、形状変更の必要性につながった実際のコンテキストを確認しないと、何を変更する必要があるかを判断するのは困難です。
質問の例は次のとおりです。
numpy.dot(M[:,0], numpy.ones((1, R)))
しかし、これは現実的ではありません。まず、この表現:
M[:,0].sum()
結果はより簡単に計算されます。第二に、列 0 には本当に何か特別なものがあるのでしょうか? おそらく実際に必要なのは次のようになります。
M.sum(axis=0)