ここでは hdf5を使用した行列の乗算 大きな行列の乗算にhdf5(pytables)を使用していますが、hdf5を使用するとプレーンnumpy.dotを使用した場合よりも高速に動作し、RAMに行列を格納するので驚いたのはなぜですかこの動作の?
また、Pythonでの行列乗算には、小さなブロック行列の乗算にnumpy.dotをまだ使用しているため、より高速な関数があるかもしれません。
ここにいくつかのコードがあります:
行列がRAMに収まると仮定します。行列10 * 1000 x 1000でテストします。
デフォルトのnumpyを使用します(BLASライブラリはないと思います)。単純なnumpy配列はRAMにあります:時間9.48
RAMにA、B、ディスクにCの場合:時間1.48
ディスク上でA、B、Cの場合:時間372.25
MKLでnumpyを使用すると、結果は0.15,0.45,43.5になります。
結果は妥当なように見えますが、1番目のケースでブロック乗算が高速である理由(A、BをRAMに格納する場合)がまだわかりません。
n_row=1000
n_col=1000
n_batch=10
def test_plain_numpy():
A=np.random.Rand(n_row,n_col)# float by default?
B=np.random.Rand(n_col,n_row)
t0= time.time()
res= np.dot(A,B)
print (time.time()-t0)
#A,B in RAM, C on disk
def test_hdf5_ram():
rows = n_row
cols = n_col
batches = n_batch
#using numpy array
A=np.random.Rand(n_row,n_col)
B=np.random.Rand(n_col,n_row)
#settings for all hdf5 files
atom = tables.Float32Atom() #if store uint8 less memory?
filters = tables.Filters(complevel=9, complib='blosc') # tune parameters
Nchunk = 128 # ?
chunkshape = (Nchunk, Nchunk)
chunk_multiple = 1
block_size = chunk_multiple * Nchunk
#using hdf5
fileName_C = 'CArray_C.h5'
shape = (A.shape[0], B.shape[1])
h5f_C = tables.open_file(fileName_C, 'w')
C = h5f_C.create_carray(h5f_C.root, 'CArray', atom, shape, chunkshape=chunkshape, filters=filters)
sz= block_size
t0= time.time()
for i in range(0, A.shape[0], sz):
for j in range(0, B.shape[1], sz):
for k in range(0, A.shape[1], sz):
C[i:i+sz,j:j+sz] += np.dot(A[i:i+sz,k:k+sz],B[k:k+sz,j:j+sz])
print (time.time()-t0)
h5f_C.close()
def test_hdf5_disk():
rows = n_row
cols = n_col
batches = n_batch
#settings for all hdf5 files
atom = tables.Float32Atom() #if store uint8 less memory?
filters = tables.Filters(complevel=9, complib='blosc') # tune parameters
Nchunk = 128 # ?
chunkshape = (Nchunk, Nchunk)
chunk_multiple = 1
block_size = chunk_multiple * Nchunk
fileName_A = 'carray_A.h5'
shape_A = (n_row*n_batch, n_col) # predefined size
h5f_A = tables.open_file(fileName_A, 'w')
A = h5f_A.create_carray(h5f_A.root, 'CArray', atom, shape_A, chunkshape=chunkshape, filters=filters)
for i in range(batches):
data = np.random.Rand(n_row, n_col)
A[i*n_row:(i+1)*n_row]= data[:]
rows = n_col
cols = n_row
batches = n_batch
fileName_B = 'carray_B.h5'
shape_B = (rows, cols*batches) # predefined size
h5f_B = tables.open_file(fileName_B, 'w')
B = h5f_B.create_carray(h5f_B.root, 'CArray', atom, shape_B, chunkshape=chunkshape, filters=filters)
sz= rows/batches
for i in range(batches):
data = np.random.Rand(sz, cols*batches)
B[i*sz:(i+1)*sz]= data[:]
fileName_C = 'CArray_C.h5'
shape = (A.shape[0], B.shape[1])
h5f_C = tables.open_file(fileName_C, 'w')
C = h5f_C.create_carray(h5f_C.root, 'CArray', atom, shape, chunkshape=chunkshape, filters=filters)
sz= block_size
t0= time.time()
for i in range(0, A.shape[0], sz):
for j in range(0, B.shape[1], sz):
for k in range(0, A.shape[1], sz):
C[i:i+sz,j:j+sz] += np.dot(A[i:i+sz,k:k+sz],B[k:k+sz,j:j+sz])
print (time.time()-t0)
h5f_A.close()
h5f_B.close()
h5f_C.close()
np.dot
は [〜#〜] blas [〜#〜] にディスパッチします
float32
、float64
、complex32
またはcomplex64
のいずれかのdtypeがあり、それ以外の場合は、独自の低速な行列乗算ルーチンをデフォルトで使用します。
BLASリンケージの確認について説明します ここ 。つまり、NumPyインストールに_dotblas.so
または類似のファイルがあるかどうかを確認します。存在する場合は、リンク先のBLASライブラリを確認してください。参照BLASは遅く、ATLASは速く、OpenBLASやIntel MKLなどのベンダー固有のバージョンはさらに高速です。マルチスレッドのBLAS実装に注意してください。Pythonのmultiprocessing
を使って うまく再生しないでください です。
次に、配列のflags
を調べて、データの配置を確認します。 1.7.2より前のバージョンのNumPyでは、np.dot
への引数は両方ともC順である必要があります。 NumPy> = 1.7.2では、Fortran配列の特別なケースが導入されているので、これはそれほど重要ではありません。
>>> X = np.random.randn(10, 4)
>>> Y = np.random.randn(7, 4).T
>>> X.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
>>> Y.flags
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
NumPyがBLASにリンクされていない場合は、(簡単に)再インストールするか、(ハード)SciPyのBLAS gemm
(一般化行列乗算)関数を使用します。
>>> from scipy.linalg import get_blas_funcs
>>> gemm = get_blas_funcs("gemm", [X, Y])
>>> np.all(gemm(1, X, Y) == np.dot(X, Y))
True
これは簡単に見えますが、エラーチェックはほとんど行わないため、何をしているのかを本当に理解している必要があります。