np.dot
2つの'float32'
2D配列を取得するとします。
res = np.dot(a, b) # see CASE 1
print(list(res[0])) # list shows more digits
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
数字。例外として、次のものを変更できます。
ケース1:スライスa
np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')
for i in range(1, len(a)):
print(list(np.dot(a[:i], b)[0])) # full shape: (i, 6)
[-0.9044868, -1.1708502, 0.90713596, 3.5594249, 1.1374012, -1.3826287]
[-0.90448684, -1.1708503, 0.9071359, 3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.9071359, 3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
印刷されたスライスは、まったく同じ数を掛けたものから派生したものですが、結果は異なります。
ケース2:
a
の1Dバージョンを平坦化しb
、次にスライスしますa
。
np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(1, 6).astype('float32')
for i in range(1, len(a)):
a_flat = np.expand_dims(a[:i].flatten(), -1) # keep 2D
print(list(np.dot(a_flat, b)[0])) # full shape: (i*6, 6)
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
ケース3:より強力な制御。関与しないすべてのゼロを0に設定します。CASE1 a[1:] = 0
コードに追加します。結果:矛盾が持続します。
ケース4:以外のインデックスをチェックします[0]
。の場合と同様に[0]
、結果は作成時点から一定数の配列拡大を安定させ始めます。出力
np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')
for j in range(len(a) - 2):
for i in range(1, len(a)):
res = np.dot(a[:i], b)
try: print(list(res[j]))
except: pass
print()
したがって、2D * 2Dの場合、結果は異なりますが、1D * 1Dでは一貫しています。私のいくつかのリーディングから、これは単純な加算を使用した1D-1Dから生じているように見えますが、2D-2Dは「より洗練された」パフォーマンスを高める加算を使用します。それにもかかわらず、1がa
セットの「しきい値」を超えてスライスされると、なぜ差異が消えるのか理解できません。とが大きいほどa
、b
このしきい値は後であるように見えますが、常に存在します。
すべてが言った:np.dot
ND-NDアレイに対してなぜ不正確(そして矛盾)なのか?関連Git
追加情報:
- 環境:Win-10 OS、Python 3.7.4、Spyder 3.3.6 IDE、Anaconda 3.0 2019/10
- CPU:i7-7700HQ 2.8 GHz
- Numpy v1.16.5
考えられる原因ライブラリ:Numpy MKL -BLASSライブラリ。注目してくれたBi Ricoに感謝
ストレステストコード:前述のように、不一致は、より大きな配列での頻度で悪化します。上記が再現可能でない場合は、以下を再現する必要があります(そうでない場合は、より大きな寸法を試してください)。私の出力
np.random.seed(1)
a = (0.01*np.random.randn(9, 9999)).astype('float32') # first multiply then type-cast
b = (0.01*np.random.randn(9999, 6)).astype('float32') # *0.01 to bound mults to < 1
for i in range(1, len(a)):
print(list(np.dot(a[:i], b)[0]))
問題の重大度:表示された差異は「小さい」ものですが、数十億の数が数秒で乗算され、ランタイム全体で何兆もあるニューラルネットワークで動作している場合は、もはやそうではありません。報告されたモデルの精度は、このスレッドごとに、全体の数十パーセントの違いがあります。
以下は基本的に何モデルへの供給に起因する配列のGIFですa[0]
/ wは、len(a)==1
対はlen(a)==32
:
Paulのテストによると他のプラットフォームの結果:
ケース1の複製(一部):
- Google Colab VM-インテルXeon 2.3 G-Hz-Jupyter-Python 3.6.8
- Win-10 Pro Dockerデスクトップ-Intel i7-8700K-jupyter / scipy-notebook-Python 3.7.3
- Ubuntu 18.04.2 LTS + Docker-AMD FX-8150-jupyter / scipy-notebook-Python 3.7.3
注:これらのエラーは上記のエラーよりもはるかに低くなります。最初の行の2つのエントリは、他の行の対応するエントリから最下位桁が1ずれています。
再現されていないケース1:
- Ubuntu 18.04.3 LTS-Intel i7-8700K-IPython 5.5.0-Python 2.7.15+および3.6.8(2テスト)
- Ubuntu 18.04.3 LTS-Intel i5-3320M-IPython 5.5.0-Python 2.7.15+
- Ubuntu 18.04.2 LTS-AMD FX-8150-IPython 5.5.0-Python 2.7.15rc1
注:
- リンクコラボノートブックとjupyter環境は私のシステムで観察されるよりも(そして唯一の最初の2行分の)はるかに小さい不一致を示しています。また、ケース2では(まだ)不正確さが示されていません。
- この非常に限られたサンプルの中で、現在の(Docker化された)Jupyter環境は、IPython環境よりも影響を受けやすくなっています。
np.show_config()
投稿するには長すぎますが、要約すると、IPython envはBLAS / LAPACKベースです。ColabはOpenBLASベースです。IPython Linux環境では、BLASライブラリはシステムにインストールされます-JupyterおよびColabでは、/ opt / conda / libから取得されます
更新:受け入れられた答えは正確ですが、広く不完全です。コードレベルで動作を説明できる人、つまり、が使用する正確なアルゴリズムnp.dot
、および上記の結果で観察された「一貫性のない矛盾」をどのように説明するかについては、疑問が残ります(コメントも参照)。ここに私の解読を超えたいくつかの直接的な実装があります:sdot.c - arraytypes.c.src
ndarrays
通常、数値精度の低下を無視するための一般的なアルゴリズム。単純にreduce-sum
するために、各軸に沿ってそれらを実行するため、操作の順序は最適な順序ではない可能性があります...精度エラーを気にする場合は、同様に使用できることに注意してくださいfloat64