Mengapa np.dot tidak tepat? (array n-redup)

15

Misalkan kita mengambil np.dotdua '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:] = 0ke 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 adipotong melewati 'ambang'; semakin besar adan b, kemudian ambang ini tampaknya terletak, tetapi selalu ada.

Semua berkata: mengapa tidak np.dottepat (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)==1vs 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

OverLordGoldDragon
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Samuel Liew
Algoritma umum untuk ndarraysbiasanya mengabaikan kehilangan presisi numerik. Karena untuk kesederhanaannya reduce-sumdi setiap sumbu, urutan operasi mungkin bukan yang optimal ... Perhatikan bahwa jika Anda keberatan dengan kesalahan presisi Anda sebaiknya menggunakanfloat64
Vitor SRG
Saya mungkin tidak punya waktu untuk meninjau besok, jadi memberi hadiah itu sekarang.
Paul
@ Paul Itu akan diberikan secara otomatis ke jawaban dengan suara tertinggi - tapi baiklah, terima kasih telah memberi tahu
OverLordGoldDragon

Jawaban:

7

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.

mmj
sumber
2
Terima kasih atas tautannya, tampaknya berisi info yang relevan - jawaban Anda, bagaimanapun, bisa lebih detail, sejauh ini adalah parafrase komentar di bawah pertanyaan. Misalnya, SO yang ditautkan menunjukkan Ckode langsung , dan memberikan penjelasan tingkat algoritme, sehingga mengarah ke arah yang benar.
OverLordGoldDragon
Itu juga bukan "tidak dapat dihindari", seperti yang ditunjukkan di bagian bawah pertanyaan - dan tingkat ketidaktepatan bervariasi di seluruh lingkungan, yang tetap tidak dapat dijelaskan
OverLordGoldDragon
1
@OverLordGoldDragon: (1) Contoh sepele dengan tambahan: ambil nomor n, ambil nomor ksehingga itu di bawah ketepatan kangka mantissa terakhir. Untuk mengapung asli Python, n = 1.0dan k = 1e-16bekerja. Sekarang mari ks = [k] * 100. Lihat itu sum([n] + ks) == n, sementara sum(ks + [n]) > n, itu, urutan penjumlahan penting. (2) CPU modern memiliki beberapa unit untuk menjalankan operasi floating-point (FP) secara paralel, dan urutan yang a + b + c + ddihitung pada CPU tidak ditentukan, bahkan jika perintah a + bdatang sebelumnya c + ddalam kode mesin.
9000
1
@OverLordGoldDragon Anda harus menyadari bahwa sebagian besar angka yang Anda minta untuk ditangani oleh program Anda tidak dapat diwakili secara tepat oleh titik mengambang. Coba format(0.01, '.30f'). Jika bahkan angka sederhana seperti 0.01tidak 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.
mmj
2
@OverLordGoldDragon re: ilmu hitam. Ada makalah yang wajib dibaca di beberapa CS MOOCs. Ingatan saya tidak terlalu bagus, tapi saya rasa ini dia: itu.dk/~sestoft/bachelor/IEEE754_article.pdf
Paul