Np.dot neden kesin değil? (n-dim dizileri)


15

np.dotİki 'float32'2D diziyi aldığımızı varsayalım :

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]

Sayılar. Dışında değişebilirler:


DURUM 1 : dilima

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]

Yazdırılan dilim aynı sayılarla çarpılmış olsa da sonuçlar farklılık gösterir.


DURUM 2 : düzleştirin a, 1D sürümünü alın b, sonra dilimleyin 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]

DURUM 3 : daha güçlü kontrol; dahil olmayan tüm girişleri sıfıra ayarlayın : a[1:] = 0CASE 1 koduna ekleyin . Sonuç: tutarsızlıklar devam ediyor.


DURUM 4 : dışındaki endeksleri kontrol edin [0]; gibi [0], sonuçlar, sabit bir dizi büyütme sayısını yaratma noktalarından sabitlemeye başlar. Çıktı

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()

Bu nedenle, 2D * 2D durum için sonuçlar farklıdır - ancak 1D * 1D için tutarlıdır. Bazı okumalarımda, bu basit ekleme kullanarak 1D-1D'den kaynaklanıyor gibi görünüyor, oysa 2D-2D daha az hassas olabilen 'meraklı', performansı artıran eklentiyi kullanıyor (örn. Çiftli ekleme bunun tersini yapıyor). Yine de, bir zamanlar abelirlenmiş bir 'eşiği' aştığında, tutarsızlıkların neden kaybolduğunu anlayamıyorum ; daha büyük ave bdaha sonra bu eşik yalan söylüyor gibi görünüyor, ama her zaman var.

Hepsi dedi: np.dotND-ND dizileri için neden kesin olmayan (ve tutarsız)? İlgili Git


Ek bilgi :

  • Ortam : Win-10 İşletim Sistemi, Python 3.7.4, Spyder 3.3.6 IDE, Anaconda 3.0 2019/10
  • İşlemci : i7-7700HQ 2.8 GHz
  • Numpy v1.16.5

Olası suçlu kütüphanesi : Numpy MKL - ayrıca BLASS kütüphaneleri; Bi Rico'ya kaydettiği için teşekkürler


Gerilme testi kodu : belirtildiği gibi, daha büyük dizilerle frekanslarda tutarsızlıklar şiddetlenir; eğer yukarıdaki tekrarlanamazsa, aşağıda olmalıdır (eğer değilse, daha büyük loşları deneyin). Çıktı

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]))

Sorun şiddeti : gösterilen tutarsızlıklar 'küçük', ancak milyarlarca sayının birkaç saniye içinde çarpıldığı ve tüm çalışma süresi boyunca trilyonların bulunduğu bir sinir ağında artık çalışmıyor; bildirilen model doğruluğu, bu iş parçacığı başına yüzde 10'ların tamamından farklıdır .

Aşağıda, temelde a[0]w / len(a)==1vs. olan bir modele beslenmeden kaynaklanan diziler bir gif len(a)==32:


Paul'ün testi sayesinde diğer PLATFORMS sonuçları :

Durum 1 yeniden üretildi (kısmen) :

  • Google Colab VM - Intel Xeon 2.3 G-Hz - Jupyter - Python 3.6.8
  • Win-10 Pro Docker Masaüstü Bilgisayar - 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

Not : bunlar yukarıda gösterilenden çok daha düşük hata verir; ilk satırdaki iki giriş, diğer satırlardaki karşılık gelen girişlerden en az anlamlı basamakta 1 ile kapalıdır.

Durum 1 çoğaltılamadı :

  • Ubuntu 18.04.3 LTS - Intel i7-8700K - IPython 5.5.0 - Python 2.7.15+ ve 3.6.8 (2 test)
  • 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

Notlar :

  • Bağlantılı CoLab dizüstü ve jupyter ortamları sistemimde görülmektedir daha (ve sadece ilk iki sıra için) çok daha az farklılık gösterir. Ayrıca, Durum 2 hiçbir zaman (henüz) kesinsizlik göstermemiştir.
  • Bu çok sınırlı örnekte, mevcut (Dockerized) Jupyter ortamı IPython ortamından daha duyarlıdır.
  • np.show_config()yayınlamak için çok uzun, ancak özet olarak: IPython envleri BLAS / LAPACK tabanlıdır; Colab OpenBLAS tabanlıdır. IPython Linux envlerinde, BLAS kütüphaneleri sisteme kurulur - Jupyter ve Colab'da / opt / conda / lib'den gelirler

GÜNCELLEME : Kabul edilen cevap doğru, ancak geniş ve eksik. Soru, davranışı kod düzeyinde açıklayabilen herkese açıktır - yani, kullanılan kesin bir algoritma np.dotve yukarıdaki sonuçlarda gözlemlenen 'tutarlı tutarsızlıkları' nasıl açıkladı (ayrıca bkz. Yorumlar). İşte benim deşifre ötesinde bazı doğrudan uygulamalar: sdot.c - arraytypes.c.src


Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Samuel Liew

Genel algoritmalar ndarraysgenellikle sayısal hassasiyet kaybını göz ardı eder. Çünkü reduce-sumher eksen boyunca basitlik için , işlemlerin sırası en uygun olmayabilir ... Hassas hatayı düşünürseniz de kullanabilirsinizfloat64
Vitor SRG

Yarın incelemek için zamanım olmayabilir, bu yüzden şimdi ödüllendirin.
Paul

@Paul Yine de en yüksek oyu alan cevaba otomatik olarak verilecekti - ama tamam, bildirim için teşekkürler
OverLordGoldDragon

Yanıtlar:


7

Bu kaçınılmaz sayısal kesinsizlik gibi görünüyor. Açıklandığı gibi burada , NumPy matris çoğalması için bir yüksek optimize dikkatle ayarlanmış BLAS yöntemini kullanır . Bu, muhtemelen 2 matrisi çarpmak için takip edilen işlem dizisinin (toplam ve ürünler), matrisin boyutu değiştiğinde değiştiği anlamına gelir.

Daha açık olmaya çalışarak, matematiksel olarak , elde edilen matrisin her bir elemanının iki vektörün nokta ürünü (eşit uzunlukta sayı dizileri) olarak hesaplanabileceğini biliyoruz . Ancak bu olup NumPy elde edilen matris bir öğe hesaplama yöntemi. Infact, Strassen algoritması gibi, daha satır sütun nokta ürününü doğrudan hesaplamadan aynı sonucu elde eden daha verimli ancak karmaşık algoritmalar vardır .

Eleman olsa bile, bu tür algoritmalar kullanıldığında ij bir elde edilen matris bir C = AB matematiksel bir nokta olarak tayin edilir i-inci satır A ile j'nci sütun B ile çarpın bir matris halinde A2 sahip aynı i-inci gibi ham a bir matris ile B2 aynı olan j'nci sütunun B , eleman C2 ij aslında bütün bağlıdır işlemlerin farklı bir dizi (aşağıdaki hesaplanacaktır A2 ve B2 matrisler), muhtemelen farklı sayısal hatalara yol açar.

Bu nedenle, matematiksel olarak C ij = C2 ij (CASE 1'inizde olduğu gibi) bile, hesaplamalarda algoritmanın ardından gelen farklı işlem dizisi (matris boyutundaki değişiklik nedeniyle) farklı sayısal hatalara yol açar. Sayısal hata, ortama bağlı olarak biraz farklı sonuçları ve bazı durumlarda bazı ortamlarda sayısal hatanın bulunmayabileceğini de açıklar.


2
Bağlantı için teşekkürler, ilgili bilgileri içeriyor gibi görünüyor - ancak cevabınız daha ayrıntılı olabilir, şu ana kadar sorunun altındaki yorumların bir açıklamasıdır. Örneğin, bağlı SO doğrudan Ckodu gösterir ve algoritma düzeyinde açıklamalar sağlar, böylece doğru yönde ilerler.
OverLordGoldDragon

Sorunun altında da görüldüğü gibi "kaçınılmaz" değildir - ve belirsizliğin kapsamı açıklanamayan ortamlar arasında değişiklik gösterir
OverLordGoldDragon

1
@OverLordGoldDragon: (1) Toplama içeren önemsiz bir örnek: sayı al n, numarayı son mantis rakamının khassasiyetinin altında olacak şekilde al k. Python'un yerli yüzer n = 1.0ve k = 1e-16çalışır. Şimdi izin ver ks = [k] * 100. Bir sum([n] + ks) == nyandan sum(ks + [n]) > nda, toplama düzeninin önemli olduğunu görün. (2) Modern CPU'ların kayan nokta (FP) işlemlerini paralel olarak yürütmek için birkaç birimi vardır a + b + c + dve komut makine kodunda daha a + bönce gelse bile, CPU'da hesaplanma sırası tanımlanmaz c + d.
9000

1
@OverLordGoldDragon Programınızdan ilgilenmesini istediğiniz sayıların çoğunun tam olarak bir kayan nokta ile temsil edilemeyeceğinin farkında olmalısınız. Deneyin format(0.01, '.30f'). Gibi basit bir sayı bile 0.01tam olarak bir NumPy kayan nokta ile temsil edilemiyorsa, cevabımın noktasını anlamak için NumPy matris çarpma algoritmasının derin detaylarını bilmeye gerek yoktur; yani farklı başlangıç ​​matrisleri farklı işlem dizilerine yol açar , böylece matematiksel olarak eşit sonuçlar sayısal hatalar nedeniyle küçük bir miktar farklılık gösterebilir.
mmj

2
@OverLordGoldDragon yeniden: kara büyü. Birkaç CS MOOC'de okunması gereken bir makale var. Hatırlama
Paul
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.