0

トピックについてはnp.einsum、次の場所ですでに多くの議論を読んでいます。

np.eimsumが通常np.sumの 、 、などよりも高速である理由をさらに理解するためにnp.product(anaconda の最新の numpy バ​​ージョンであっても)、np.einsum_path最適化プロセスで何が最適化されたかを確認するために使用しています。

そうこうしていると面白い現象を発見。次の最小限の例を考えてみましょう。

import numpy as np
for i in 'int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64'.split():
    print(np.einsum_path('i->', np.empty(2**30, i))[1])

出力はすべて同じです。

  Complete contraction:  i->
         Naive scaling:  1
     Optimized scaling:  1
      Naive FLOP count:  1.074e+09
  Optimized FLOP count:  2.147e+09
   Theoretical speedup:  0.500
  Largest intermediate:  1.000e+00 elements
--------------------------------------------------------------------------
scaling                  current                                remaining
--------------------------------------------------------------------------
   1                         i->                                       ->

最適化された FLOPが増加し (計算が増えることを意味しますか?)、理論上のスピードアップが 1 よりも小さくなります (遅いことを意味します)。しかし、実際に計算の時間を計ると:

for i in 'int8 int16 int32 int64 uint8 uint16 uint32 uint64 float32 float64'.split():
    a    = np.empty(2**27, i)
    raw  = %timeit -qon9 a.sum()
    noOp = %timeit -qon9 np.einsum('i->', a, optimize=False)
    op   = %timeit -qon9 np.einsum('i->', a, optimize='greedy')
    print(i, raw.average/op.average, noOp.average/op.average, sep='\t')

「ネイティブ」タイミングを最適化されたタイミングで割った値に対応する 2 番目の列を見ると、それらはすべて 1 に近く、最適化によって速度が低下していないことを意味します。

int8    4.485133392283354   1.0205873691331475
int16   3.7817373109729213  0.9528030137222752
int32   1.3760725925789292  1.0741615462167338
int64   1.0793509548186524  1.0076602576129605
uint8   4.509893894635594   0.997277624256872
uint16  3.964949791428885   0.9914991211913878
uint32  1.3054813163356085  1.009475242303559
uint64  1.0747670688044795  1.0082522386805526
float32 2.4105510701565636  0.9998241152368149
float64 2.1957241421227556  0.9836838487664662

私はnp.einsum_path、それがより多くのFLOPを必要とし、より遅いと傷が言うのだろうかと思っていますか? タイミングは FLOP の数から直接計算されるので、これら 2 つのベンチマークは基本的に同じことを指していると思います。

np.einsum_pathところで、上記の結果が異常であることを納得させるために「通常」どのように動作するかを示す例を添付します。

a = np.empty((64, 64))
print(np.einsum_path('ij,jk,kl->il', a, a, a)[1])
noOp = %timeit -qon99 np.einsum('ij,jk,kl->il', a, a, a, optimize=False)
op   = %timeit -qon99 np.einsum('ij,jk,kl->il', a, a, a, optimize='greedy')
print('Actual speedup:', noOp.average / op.average)

出力:

  Complete contraction:  ij,jk,kl->il
         Naive scaling:  4
     Optimized scaling:  3
      Naive FLOP count:  5.033e+07
  Optimized FLOP count:  1.049e+06
   Theoretical speedup:  48.000
  Largest intermediate:  4.096e+03 elements
--------------------------------------------------------------------------
scaling                  current                                remaining
--------------------------------------------------------------------------
   3                   jk,ij->ik                                kl,ik->il
   3                   ik,kl->il                                   il->il
Actual speed up: 90.33518444642904
4

1 に答える 1