Misalkan kita mengambil np.dot
dua 'float32'
array 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]
Angka Kecuali, mereka dapat berubah:
KASUS 1 : irisana
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]
Hasil berbeda, meskipun potongan yang dicetak berasal dari angka yang sama persis dikalikan.
KASUS 2 : ratakan
a
, ambil versi 1D b
, lalu iris 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]
KASUS 3 : kontrol yang lebih kuat; setel semua entires yang tidak terlibat menjadi nol : tambahkan a[1:] = 0
ke kode CASE 1. Hasil: perbedaan tetap ada.
KASUS 4 : periksa indeks selain [0]
; seperti untuk [0]
, hasil mulai menstabilkan # pembesaran array tetap dari titik kreasi mereka. Keluaran
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()
Oleh karena itu, untuk kasus 2D * 2D, hasilnya berbeda - tetapi konsisten untuk 1D * 1D. Dari beberapa bacaan saya, ini tampaknya berasal dari 1D-1D menggunakan penambahan sederhana, sedangkan 2D-2D menggunakan 'pelamun', penambahan peningkatan kinerja yang mungkin kurang tepat (misalnya penambahan berpasangan melakukan sebaliknya). Meskipun demikian, saya tidak dapat memahami mengapa perbedaan menghilang jika saya pernah a
dipotong melewati 'ambang'; semakin besar a
dan b
, kemudian ambang ini tampaknya terletak, tetapi selalu ada.
Semua berkata: mengapa tidak np.dot
tepat (dan tidak konsisten) untuk array ND-ND? Git yang relevan
Info tambahan :
- Lingkungan : 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
Kemungkinan perpustakaan pelakunya : Numpy MKL - juga perpustakaan BLASS; terima kasih kepada Bi Rico untuk mencatat
Kode uji stres : seperti yang disebutkan, perbedaan memperburuk frekuensi dengan array yang lebih besar; jika di atas tidak dapat direproduksi, di bawah ini seharusnya (jika tidak, coba redup yang lebih besar). Output saya
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]))
Tingkat keparahan masalah : perbedaan yang ditunjukkan 'kecil', tetapi tidak lagi ketika beroperasi pada jaringan saraf dengan miliaran angka dikalikan beberapa detik, dan triliunan selama runtime keseluruhan; keakuratan model yang dilaporkan berbeda dengan keseluruhan 10 persen, per utas ini .
Di bawah ini adalah gif dari array yang dihasilkan dari pengumpanan ke model yang pada dasarnya a[0]
, dengan len(a)==1
vs len(a)==32
.:
Hasil PLATFORMS LAINNYA , sesuai dan dengan terima kasih atas pengujian Paul :
Kasus 1 direproduksi (sebagian) :
- Google Colab VM - Intel Xeon 2.3 G-Hz - Jupyter - Python 3.6.8
- Win-10 Pro Docker Desktop - 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
Catatan : ini menghasilkan kesalahan yang jauh lebih rendah daripada yang ditunjukkan di atas; dua entri pada baris pertama dimatikan oleh 1 di digit paling tidak signifikan dari entri yang sesuai di baris lain.
Kasus 1 tidak direproduksi :
- Ubuntu 18.04.3 LTS - Intel i7-8700K - IPython 5.5.0 - Python 2.7.15+ dan 3.6.8 (2 tes)
- 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
Catatan :
- The terkait CoLab notebook dan jupyter lingkungan menunjukkan perbedaan yang jauh lebih rendah (dan hanya untuk dua baris pertama) dari yang diamati pada sistem saya. Juga, Kasus 2 tidak pernah (belum) menunjukkan ketidaktepatan.
- Dalam sampel yang sangat terbatas ini, lingkungan Jupyter (Dockerized) saat ini lebih rentan daripada lingkungan IPython.
np.show_config()
terlalu panjang untuk diposkan, tetapi dalam ringkasan: IPython envs berbasis BLAS / LAPACK; Colab berbasis OpenBLAS. Dalam IPython Linux envs, perpustakaan BLAS diinstal-sistem - di Jupyter dan Colab, mereka berasal dari / opt / conda / lib
UPDATE : jawaban yang diterima akurat, tetapi luas dan tidak lengkap. Pertanyaannya tetap terbuka bagi siapa saja yang dapat menjelaskan perilaku di tingkat kode - yaitu, algoritma yang tepat digunakan oleh np.dot
, dan bagaimana hal itu menjelaskan 'ketidakkonsistenan yang konsisten' yang diamati pada hasil di atas (juga lihat komentar). Berikut adalah beberapa implementasi langsung di luar pengartian saya: sdot.c - arraytypes.c.src
ndarrays
biasanya mengabaikan kehilangan presisi numerik. Karena untuk kesederhanaannyareduce-sum
di setiap sumbu, urutan operasi mungkin bukan yang optimal ... Perhatikan bahwa jika Anda keberatan dengan kesalahan presisi Anda sebaiknya menggunakanfloat64
Jawaban:
Ini seperti ketidaktepatan angka yang tidak dapat dihindari. Seperti dijelaskan di sini , NumPy menggunakan metode BLAS yang sangat dioptimalkan dengan hati-hati untuk perkalian matriks . Ini berarti bahwa mungkin urutan operasi (jumlah dan produk) diikuti untuk mengalikan 2 matriks, berubah ketika ukuran matriks berubah.
Mencoba untuk menjadi lebih jelas, kita tahu bahwa, secara matematis , setiap elemen dari matriks yang dihasilkan dapat dihitung sebagai produk titik dari dua vektor (urutan angka dengan panjang yang sama). Tapi ini bukan bagaimana NumPy menghitung elemen dari matriks yang dihasilkan. Infact ada algoritma yang lebih efisien tetapi kompleks, seperti algoritma Strassen , yang memperoleh hasil yang sama tanpa menghitung secara langsung produk dot baris-kolom.
Saat menggunakan algoritma seperti itu, bahkan jika elemen C ij dari matriks yang dihasilkan C = AB secara matematis didefinisikan sebagai produk titik dari baris ke-i dari A dengan kolom ke-j dari B , jika Anda mengalikan matriks A2 yang memiliki sama -i baris sebagai A dengan matriks B2 memiliki sama -j kolom sebagai B , elemen C2 ij akan benar-benar dihitung sebagai berikut urutan yang berbeda dari operasi (yang tergantung pada seluruh A2 dan B2 matriks), mungkin mengarah ke kesalahan numerik yang berbeda.
Itu sebabnya, bahkan jika secara matematis C ij = C2 ij (seperti dalam CASE 1 Anda), urutan operasi yang berbeda diikuti oleh algoritma dalam perhitungan (karena perubahan ukuran matriks) menyebabkan kesalahan numerik yang berbeda. Kesalahan numerik menjelaskan juga hasil yang sedikit berbeda tergantung pada lingkungan dan fakta bahwa, dalam beberapa kasus, untuk beberapa lingkungan, kesalahan numerik mungkin tidak ada.
sumber
C
kode langsung , dan memberikan penjelasan tingkat algoritme, sehingga mengarah ke arah yang benar.n
, ambil nomork
sehingga itu di bawah ketepatank
angka mantissa terakhir. Untuk mengapung asli Python,n = 1.0
dank = 1e-16
bekerja. Sekarang mariks = [k] * 100
. Lihat itusum([n] + ks) == n
, sementarasum(ks + [n]) > n
, itu, urutan penjumlahan penting. (2) CPU modern memiliki beberapa unit untuk menjalankan operasi floating-point (FP) secara paralel, dan urutan yanga + b + c + d
dihitung pada CPU tidak ditentukan, bahkan jika perintaha + b
datang sebelumnyac + d
dalam kode mesin.format(0.01, '.30f')
. Jika bahkan angka sederhana seperti0.01
tidak dapat diwakili secara tepat oleh titik apung NumPy, tidak perlu mengetahui detail mendalam dari algoritma penggandaan matriks NumPy untuk memahami titik jawaban saya; yang berbeda matriks awal mengarah ke urutan operasi yang berbeda , sehingga hasil yang sama secara matematis dapat berbeda dengan jumlah kecil karena kesalahan numerik.