2016-06-21 34 views
5

Ho un set di dati di grandi dimensioni: 20.000 x 40.000 come array numpy. L'ho salvato come file sottaceto.Come caricare una riga alla volta da un file pickle?

Invece di leggere questo enorme set di dati in memoria, mi piacerebbe leggere solo alcune righe (diciamo 100) alla volta, da utilizzare come un minibatch.

Come posso leggere solo alcune righe scelte a caso (senza sostituzione) da un file di pickle?

+3

Memorizzarlo in un altro formato che consente l'accesso casuale o incrementale. – martineau

+0

Che cosa mi consiglia? Posso convertirlo da pickle in un altro formato senza doverlo aprire? – StatsSorceress

+0

Dovrai caricarlo e scaricarlo di nuovo in un altro formato –

risposta

3

Poiché non si conoscono i meccanismi interni di pickle, è necessario utilizzare un altro metodo di memorizzazione. Lo script seguente utilizza le funzioni tobytes() per salvare i dati in linea in un file raw.

Poiché la lunghezza di ogni riga è nota, il suo offset nel file può essere calcolato e accessibile tramite seek() e read(). Successivamente, viene riconvertito in una matrice con la funzione frombuffer().

Il grande disclaimer tuttavia è che la dimensione dell'array non è stata salvata (potrebbe essere aggiunta anche se richiede alcune ulteriori complicazioni) e che questo metodo potrebbe non essere portatile come un array decapato.

Come sottolineato da @PadraicCunningham nel suo comment, un memmap è probabilmente una soluzione alternativa ed elegante.

Note sulle prestazioni: Dopo aver letto i commenti ho fatto un breve punto di riferimento. Sulla mia macchina (16 GB di RAM, SSD crittografato) sono stato in grado di eseguire 40000 letture di righe casuali in 24 secondi (con una matrice 20000x40000 ovviamente, non il 10x10 dell'esempio).

from __future__ import print_function 
import numpy 
import random 

def dumparray(a, path): 
    lines, _ = a.shape 
    with open(path, 'wb') as fd: 
     for i in range(lines): 
      fd.write(a[i,...].tobytes()) 

class RandomLineAccess(object): 
    def __init__(self, path, cols, dtype): 
     self.dtype = dtype 
     self.fd = open(path, 'rb') 
     self.line_length = cols*dtype.itemsize 

    def read_line(self, line): 
     offset = line*self.line_length 
     self.fd.seek(offset) 
     data = self.fd.read(self.line_length) 

     return numpy.frombuffer(data, self.dtype) 

    def close(self): 
     self.fd.close() 


def main(): 
    lines = 10 
    cols = 10 
    path = '/tmp/array' 

    a = numpy.zeros((lines, cols)) 
    dtype = a.dtype 

    for i in range(lines): 
     # add some data to distinguish lines 
     numpy.ndarray.fill(a[i,...], i) 

    dumparray(a, path) 
    rla = RandomLineAccess(path, cols, dtype) 

    line_indices = list(range(lines)) 
    for _ in range(20): 
     line_index = random.choice(line_indices) 
     print(line_index, rla.read_line(line_index)) 

if __name__ == '__main__': 
    main() 
4

È possibile scrivere sottaceti in modo incrementale in un file, che permette di caricare loro incrementale pure.

Prendete il seguente esempio. Qui, iteriamo sopra gli elementi di un elenco e sottaceto ognuno a turno.

>>> import cPickle 
>>> myData = [1, 2, 3] 
>>> f = open('mydata.pkl', 'wb') 
>>> pickler = cPickle.Pickler(f) 
>>> for e in myData: 
...  pickler.dump(e) 
<cPickle.Pickler object at 0x7f3849818f68> 
<cPickle.Pickler object at 0x7f3849818f68> 
<cPickle.Pickler object at 0x7f3849818f68> 
>>> f.close() 

Ora possiamo eseguire lo stesso processo in ordine inverso e caricare ogni oggetto secondo necessità. Per lo lo scopo dell'esempio, diciamo che vogliamo solo il primo elemento e non desidera eseguire l'iterazione sull'intero file.

>>> f = open('mydata.pkl', 'rb') 
>>> unpickler = cPickle.Unpickler(f) 
>>> unpickler.load() 
1 

A questo punto, il flusso di file ha avanzato solo per quanto riguarda il primo oggetto. Gli oggetti rimanenti non sono stati caricati, il che è esattamente il comportamento desiderato da . A prova, puoi provare a leggere il resto del file e vedere il resto è ancora seduto lì.

>>> f.read() 
'I2\n.I3\n.' 
0

Grazie a tutti. Ho finito per trovare una soluzione alternativa (una macchina con più RAM in modo da poter effettivamente caricare il set di dati in memoria).