2013-03-15 15 views
10

Nel mio previous question, ho imparato a ridimensionare una sottoclasse ndarray in posizione. Neat. Purtroppo, che non funziona più quando la matrice che sto cercando di ridimensionare è il risultato di un calcolo:Trasferimento proprietà di dati numpy

import numpy as np 

class Foo(np.ndarray): 
    def __new__(cls,shape,dtype=np.float32,buffer=None,offset=0, 
       strides=None,order=None): 
     return np.ndarray.__new__(cls,shape,dtype,buffer,offset,strides,order) 

    def __array_prepare__(self,output,context): 
     print output.flags['OWNDATA'],"PREPARE",type(output) 
     return np.ndarray.__array_prepare__(self,output,context) 

    def __array_wrap__(self,output,context=None): 
     print output.flags['OWNDATA'],"WRAP",type(output) 

     return np.ndarray.__array_wrap__(self,output,context) 

a = Foo((32,)) 
#resizing a is no problem 
a.resize((24,),refcheck=False) 

b = Foo((32,)) 
c = Foo((32,)) 

d = b+c 
#Cannot resize `d` 
d.resize((24,),refcheck=False) 

L'uscita esatta (compresi traceback) è:

True PREPARE <type 'numpy.ndarray'> 
False WRAP <class '__main__.Foo'> 
Traceback (most recent call last): 
    File "test.py", line 26, in <module> 
    d.resize((24,),refcheck=False) 
ValueError: cannot resize this array: it does not own its data 

Penso che questo sia perché numpy crea un nuovo ndarray e lo passa a __array_prepare__. Tuttavia, a un certo punto lungo la strada, sembra che la matrice "" ottiene view-casted to my Foo type, anche se i documenti non sembrano essere al 100% chiari/precisi su questo punto. In ogni caso, dopo il casting della vista, l'output non possiede più i dati rendendo impossibile rimodellarlo sul posto (per quanto ne so).

C'è un modo, tramite una sorta di numpy voodoo (__array_prepare__, __array__) ecc. Per trasferire la proprietà dei dati all'istanza della mia sottoclasse?

+0

Su ulteriore ricerca, questo utente sembra essere [chiedendo la stessa cosa] (http://stackoverflow.com/questions/8708758/can- i-force-a-numpy-ndarray-to-take-ownership-of-its-memory), anche se da una prospettiva diversa ... È possibile che ci possa essere una risposta che funziona nella mia situazione che non funzionerebbe lì ... – mgilson

+1

È certamente una vista. La base 'ndarray' è' d.base', che possiede i dati. Come kludge puoi ridimensionare 'd.base' e ​​quindi riassegnare' d ​​= d.base.view (Foo) '. – eryksun

+0

eryksun - suoni promettenti tranne che poi potrei perdere gli altri attributi che ho aggiunto al mio campo nel frattempo. Pubblica comunque come risposta. Mi darà qualcosa per sperimentare domani. Sono un po 'troppo stanco per tentare qualcosa di più che un commento senza senso del mio codice a questo punto. – mgilson

risposta

6

non è certo una soddisfacente rispondi, ma non va bene int o un commento o ... È possibile aggirare il possesso dei dati utilizzando il parametro out di ufunc.Un esempio stupido:

>>> a = Foo((5,)) 
>>> b = Foo((5,)) 
>>> c = a + b # BAD 
True PREPARE <type 'numpy.ndarray'> 
False WRAP <class '__main__.Foo'> 
>>> c.flags.owndata 
False 

>>> c = Foo((5,)) 
>>> c[:] = a + b # BETTER 
True PREPARE <type 'numpy.ndarray'> 
False WRAP <class '__main__.Foo'> 
>>> c.flags.owndata 
True 

>>> np.add(a, b, out=c) # BEST 
True PREPARE <class '__main__.Foo'> 
True WRAP <class '__main__.Foo'> 
Foo([ 1.37754085e-38, 1.68450356e-20, 6.91042737e-37, 
     1.74735556e-04, 1.48018885e+29], dtype=float32) 
>>> c.flags.owndata 
True 

Penso che l'uscita di cui sopra è coerente con c[:] = a + b arrivare a possedere i dati a scapito di copiare in c da un array temporaneo. Ma questo non dovrebbe accadere quando si utilizza il parametro out.

Poiché si era già preoccupati dello storage intermedio nelle espressioni matematiche, potrebbe non essere una cattiva idea gestire in modo microscopico il modo in cui viene gestito. Cioè, in sostituzione di

g = a + b + np.sqrt(d*d + e*e + f*f) 

con

g = foo_like(d) # you'll need to write this function! 
np.multiply(d, d, out=g) 
g += e * e 
g += f * f 
np.sqrt(g, out=g) 
g += b 
g += a 

può risparmiare un po 'di memoria di trasferimento, e consente di proprietario dei dati. Fa gettare il mantra "readability counts" fuori dalla finestra, ma ...

+0

Sì, ho capito che si poteva usare' out'. Nel mio caso, non è il massimo se vuoi fare 'a + b * sqrt (c) ** 4' o altro. (scrivendo che tutto come chiamate di funzione non è divertente). Ma non avevo mai pensato di usare un assegnamento di slice per assicurarmi che LHS possedesse i dati. Questo è un bel tocco. – mgilson

+1

@ mgilson È solo una copia nascosta, però. – Jaime

1

Ad un certo punto lungo la strada, però, sembra che la matrice "uscita" ottiene view-casted al mio tipo Foo

Sì, ndarray.__array_prepare__ chiamate output.view, che restituisce un array che non lo fa possedere i suoi dati

Ho sperimentato un po 'e non sono riuscito a trovare un modo semplice per aggirarlo.

Mentre sono d'accordo questo comportamento non è l'ideale, almeno nel tuo caso d'uso, direi che è accettabile per d non possedere i suoi dati. Numpy usa ampiamente le viste e se insisti a evitare di creare visualizzazioni nel tuo lavoro con gli array di Numpy, stai rendendo la tua vita molto difficile.

Vorrei anche affermare che, in base alla mia esperienza, lo resize dovrebbe essere generalmente evitato. Non si dovrebbe avere alcun problema a lavorare con la vista creata se si evita lo resize ing. C'è una sensazione hacky, ed è difficile lavorare con (come si potrebbe iniziare a capire, dopo aver incontrato uno dei due errori classici quando lo si utilizza: it does not own its data. L'altro è cannot resize an array that has been referenced). (Un altro problema è descritto in questo quesion.)

Dal momento che la vostra decisione di utilizzare resize viene da una risposta alla tua altra domanda, vi posterò il resto della mia risposta there.

+1

Sono pienamente d'accordo, il semplice fatto che ci sia un 'refcheck = False' è un grande momento per giocare con il fuoco. Sembra esserci un mito che la copia sia lenta, ma" slow "è relativo e, a meno che non lo credi attentamente, è una vera differenza e il codice è molto specifico ... non ... – seberg

+0

@seberg - Per ora sto copiando i dati alla fine del operazione - Ma copiare per ogni 'ufunc' sembra una cattiva idea. – mgilson

+0

@ mgilson, e perché ne hai bisogno in primo luogo, perché devi ridurre i dati * dopo * l'ufunc così tanto che vale la pena considerare anche il ridimensionamento? E se hai davvero bisogno solo di una piccola parte, puoi copiare quella piccola parte senza una seria penalizzazione delle prestazioni. – seberg

0

ne dite:

def resize(arr, shape): 
    np.require(arr, requirements=['OWNDATA']) 
    arr.resize(shape, refcheck=False) 

Sembra di riuscire a ridimensionare (e riducendo il consumo di memoria):

import array 
import numpy as np 
import time 

class Foo(np.ndarray): 
    def __new__(cls, shape, dtype=np.float32, buffer=None, offset=0, 
       strides=None, order=None): 
     return np.ndarray.__new__(cls, shape, dtype, buffer, offset, strides, order) 

    def __array_prepare__(self, output, context): 
     print(output.flags['OWNDATA'], "PREPARE", type(output)) 
     return np.ndarray.__array_prepare__(self, output, context) 

    def __array_wrap__(self, output, context=None): 
     print(output.flags['OWNDATA'], "WRAP", type(output)) 
     output = np.ndarray.__array_wrap__(self, output, context) 
     return output 

def free_memory(): 
    """ 
    Return free memory available, including buffer and cached memory 
    """ 
    total = 0 
    with open('/proc/meminfo', 'r') as f: 
     for line in f: 
      line = line.strip() 
      if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')): 
       field, amount, unit = line.split() 
       amount = int(amount) 
       if unit != 'kB': 
        raise ValueError(
         'Unknown unit {u!r} in /proc/meminfo'.format(u=unit)) 
       total += amount 
    return total 


def gen_change_in_memory(): 
    """ 
    http://stackoverflow.com/a/14446011/190597 (unutbu) 
    """ 
    f = free_memory() 
    diff = 0 
    while True: 
     yield diff 
     f2 = free_memory() 
     diff = f - f2 
     f = f2 
change_in_memory = gen_change_in_memory().next 

def resize(arr, shape): 
    print(change_in_memory()) 
    # 0 
    np.require(arr, requirements=['OWNDATA']) 

    time.sleep(1) 
    print(change_in_memory()) 
    # 200 

    arr.resize(shape, refcheck=False) 

N = 10000000 
b = Foo((N,), buffer = array.array('f',range(N))) 
c = Foo((N,), buffer = array.array('f',range(N))) 

cede

print(change_in_memory()) 
# 0 

d = b+c 
d = np.require(d, requirements=['OWNDATA']) 

print(change_in_memory()) 
# 39136 

resize(d, (24,)) # Increases memory by 200 KiB 
time.sleep(1) 
print(change_in_memory()) 
# -39116 
+0

Le note nei documenti di 'np.require' dicono:" L'array restituito avrà i requisiti elencati ** facendo una copia se necessario. ** "Cosa pensi che stia succedendo, oltre a una copia, per far funzionare questo? Tenderei a pensare che alla fine questo è lo stesso di 'd = d.copy()', anche se non ha alcun senso. – Jaime

+0

@Jaime: Nella situazione precedente, dove 'd = b + c', non capisco perché' d' non possiede i suoi dati. Se capisci perché, apprezzerei davvero una spiegazione. Appare almeno in casi come questo, 'np.require' succede senza copiare i dati come evidenziato dall'aumento della memoria libera. – unutbu

+0

@unutbu, no copia i dati, ma cade il riferimento all'originale, quindi libera di nuovo lo stesso importo. Non possiede i suoi dati poiché si basa su una classe base ndarray, che potrebbe essere un difetto, ma ... – seberg

Problemi correlati