Supponiamo di prendere np.dot
due '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]
Numeri. Tranne che possono cambiare:
CASO 1 : fettaa
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]
I risultati differiscono, anche se la sezione stampata deriva dagli stessi stessi numeri moltiplicati.
CASO 2 : appiattire
a
, prendere una versione 1D di b
, quindi tagliare 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]
CASO 3 : controllo più forte; imposta a zero tutti gli enti non coinvolti : aggiungi a[1:] = 0
al codice CASE 1. Risultato: persistono discrepanze.
CASO 4 : controllare indici diversi da [0]
; come per [0]
, i risultati iniziano a stabilizzare un numero fisso di ingrandimenti di array dal loro punto di creazione. Produzione
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()
Quindi, per il caso 2D * 2D, i risultati differiscono, ma sono coerenti per 1D * 1D. Da alcune delle mie letture, questo sembra derivare dalla 1D-1D usando una semplice aggiunta, mentre il 2D-2D usa un'aggiunta più "fantasiosa", che può essere meno precisa (ad esempio, l'aggiunta a coppie fa il contrario). Tuttavia, non sono in grado di capire perché le discrepanze scompaiano nel caso in cui una volta a
venga superata una "soglia" fissa; più grande a
e b
, più tardi questa soglia sembra mentire, ma esiste sempre.
Tutto detto: perché è np.dot
impreciso (e incoerente) per gli array ND-ND? Git pertinente
Informazioni aggiuntive :
- Ambiente : sistema operativo Win-10, Python 3.7.4, Spyder 3.3.6 IDE, Anaconda 3.0 2019/10
- CPU : i7-7700HQ 2,8 GHz
- Numpy v1.16.5
Possibile libreria di colpevoli : Numpy MKL - anche librerie BLASS; grazie a Bi Rico per aver notato
Codice del test da sforzo : come notato, le discrepanze si aggravano in frequenza con array più grandi; se sopra non è riproducibile, sotto dovrebbe essere (in caso contrario, provare dim più grandi). La mia uscita
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]))
Gravità del problema : le discrepanze mostrate sono "piccole", ma non lo sono più quando si opera su una rete neurale con miliardi di numeri moltiplicati per alcuni secondi e trilioni durante l'intero tempo di esecuzione; l'accuratezza del modello riportata differisce per interi 10 di percentuali, per questa discussione .
Di seguito è riportato un gif di array risultante dall'alimentazione a un modello di cosa è sostanzialmente a[0]
, w / len(a)==1
vs len(a)==32
.:
ALTRI PIATTAFORME risultati, secondo e con grazie ai test di Paul :
Caso 1 riprodotto (in parte) :
- Google Colab VM - Intel Xeon 2.3 G-Hz - Jupyter - Python 3.6.8
- Desktop Docker Win-10 Pro - 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
Nota : questi producono errori molto più bassi di quelli mostrati sopra; due voci nella prima riga sono disattivate di 1 nella cifra meno significativa dalle voci corrispondenti nelle altre righe.
Caso 1 non riprodotto :
- Ubuntu 18.04.3 LTS - Intel i7-8700K - IPython 5.5.0 - Python 2.7.15+ e 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
Note :
- Il notebook Colab collegato e gli ambienti jupyter mostrano una discrepanza molto minore (e solo per le prime due righe) rispetto a quella osservata sul mio sistema. Inoltre, il caso 2 non ha mai (ancora) mostrato imprecisione.
- All'interno di questo campione molto limitato, l'attuale ambiente Jupyter (Dockerized) è più sensibile dell'ambiente IPython.
np.show_config()
troppo lungo per essere pubblicato, ma in sintesi: gli envs di IPython sono basati su BLAS / LAPACK; Colab è basato su OpenBLAS. In IPython Linux envs, le librerie BLAS sono installate dal sistema - in Jupyter e Colab provengono da / opt / conda / lib
AGGIORNAMENTO : la risposta accettata è accurata, ma ampia e incompleta. La domanda rimane aperta a chiunque sia in grado di spiegare il comportamento a livello di codice - vale a dire, un algoritmo esatto utilizzato da np.dot
e come spiega "incoerenze coerenti" osservate nei risultati precedenti (vedere anche i commenti). Ecco alcune implementazioni dirette oltre la mia decifrazione: sdot.c - arraytypes.c.src
ndarrays
solito ignorano la perdita di precisione numerica. Perché per semplicità loro reduce-sum
lungo ciascun asse, l'ordine delle operazioni potrebbe non essere quello ottimale ... Nota che se ti dispiace errore di precisione potresti anche usarefloat64