Design: più algoritmi sugli stessi grandi set di dati

4

Ho diversi algoritmi che vorrei testare con gli stessi set di dati per confrontare i loro risultati. Non so come progettarlo, quindi c'è la massima leggibilità e la massima efficienza.

Ho preso in considerazione la possibilità di creare una classe per ciascun algoritmo e dargli una copia dei dati con cui lavorare, ma non sembra che quella sia la risposta giusta:

  1. Ogni set di dati è abbastanza grande (10.000 float numpy array), quindi non voglio copiare ognuno di questi ~ 30 volte.
  2. Molti degli algoritmi hanno routine di pre-elaborazione simili (quindi la loro ripetizione per ciascun algoritmo sembra inutile)
  3. Alcuni algoritmi hanno un codice quasi identico, tranne alcuni parametri diversi.

Allo stesso tempo, avere una chiamata di funzione per algoritmo sembra anche sbagliata: come per (2), molti chiameranno le stesse funzioni di pre-elaborazione, e quindi diventa molto difficile dire chi sta chiamando chi.

Voglio essere in grado di consentire all'utente (che sarà io) di chiamare facilmente una varietà di algoritmi sui dati, mantenendo il codice il più chiaro possibile.

Continuo a pensare di aver bisogno dell'inverso di una classe; dove ogni oggetto di una classe avrà gli stessi metodi ma diversi dati, ho bisogno di qualcosa in cui ogni membro avrà gli stessi dati ma metodi diversi.

    
posta ari 09.09.2013 - 18:55
fonte

4 risposte

2

Fondamentalmente vuoi creare i grandi dati una volta e nascondili tra le invocazioni dei tuoi diversi algoritmi.

Perché funzioni, i dati che vuoi condividere non devono essere alterati dagli algoritmi che esegui.

Che ne dici di una fabbrica di caching e qualche iniezione di dipendenza?

class DataFactory(object):

  def __init__(self, ...):
    self._raw_data = None  # will be lazy-loaded and cached
    self._preprocessed_with_foo = None  # ditto
    self._preprocessed_with_bar = None  # ditto

  @propery
  def raw_data(self):
    if not self._raw_data:
      self._raw_data = loadDataSomehow(...)
    return self._raw_data

  @propery
  def foo_data(self):
    if not self._preprocessed_with_foo:
      self._preprocessed_with_foo = fooTransform(self.raw_data)
    return self._preprocessed_with_foo

  # etc

class Algo1(object):

  def __init__(self, data_factory):
    self.data_factory = data_factory

  def calculateOne(self):
    return someAlgorithm(self.data_factory.raw_data)

  def calculateFooOne(self):
    return anotherAlgorithm(self.data_factory.foo_data)

class Algo2(...): ...  # by the same pattern

Funziona così:

factory = DataFactory(...)  # nothing much happens

algo1 = Algo1(factory)  # dependency on factory is injected into algo1

print aglo1.calculateOne()  # factory loads raw data
print algo1.calculateFooOne() # factory reuses raw data, calculates foo data

algo2 = Algo2(factory)  # note: the same factory

print algo2.calculateTwo()  # assumedly reuses raw data
print algo2.calculateFooTwo()  # reuses both raw data and foo data
    
risposta data 09.09.2013 - 19:39
fonte
1
> a = np.random.randn(10000)
> %timeit a.copy()
100000 loops, best of 3: 3.9 us per loop

Quindi copiare per 30 volte richiede un tempo impercettibile. Mentre vorrei maggiori informazioni su cosa siano questi algoritmi (e raccomandiamo vivamente %timeit , insieme al resto di ipython), dubito che le copie saranno le più grandi delle tue preoccupazioni.

Mentre si è tentati di condividere i blocchi di inizializzazione, si dice già che la maggior parte del codice significativo è già separata. Suggerirei di scrivere ogni algoritmo nel modo più diretto possibile, al di sopra di qualsiasi altra considerazione.

    
risposta data 10.09.2013 - 09:41
fonte
0

dici di voler "testare" algoritmi diversi. Se vuoi metterli alla prova per scegliere il "migliore", manterrò una classe per oggetto. Inizialmente, alcuni dei codici saranno duplicati, ma allo stesso tempo, la modifica di un algoritmo non avrà alcun impatto sugli altri algoritmi e separarli alla fine sarà più semplice e aggiungere nuovi algoritmi al test .

Se alcuni algoritmi condividono la stessa elaborazione preliminare per progettazione, non c'è motivo per cui non si possa usare l'ereditarietà per quella famiglia di algoritmi e memorizzare in cache il risultato di tale pre-elaborazione. Potrebbe leggermente incrinare il confronto tra questa famiglia di algoritmi e altri algoritmi completamente indipendenti (consumo di memoria, costo della caching della CPU ...).

Ora, dico "meglio" e non so cosa significhi. Se si tratta solo di prestazioni, una copia dell'array probabilmente sarà molto più economica rispetto all'esecuzione dell'algoritmo sull'intero set di dati. E questo è qualcosa che probabilmente non dovrai fare una volta selezionato il tuo migliore.

Inoltre, dici di non voler copiare il set di dati 30 volte. Se non lo modifichi nell'algoritmo (che non dovresti), non devi copiarlo in primo luogo.

    
risposta data 10.09.2013 - 08:52
fonte
0

Per iniziare, non si dovrebbe mai pre-ottimizzare il codice; il codice che è più facile da capire e più logico è preferibile il 98% delle volte. Ma dal momento che lo stai sollevando, sospetto che tu stia riscontrando problemi.

Dato che stai facendo un punto di riferimento, dovresti averne uno:

class Benchmark(object):
    def __init__(self, data):
        self.data = data
    def __call__(self, algos):
        return (algo.test(self.data) for algo in algos)

benchmark = Benchmark(get_data())
benchmark([Algo1(), Algo2(), ..]

Tuttavia, mi rendo conto leggendo nuovamente la domanda che preferiresti avere qualcosa di più interattivo.

Quando si tratta del codice comune tra gli algoritmi, dovresti avere un'eredità adeguata per ordinarli tutti (normalmente è una pratica scorretta copiare del codice, quindi lavorare contro quello spingendo più codice possibile in alto nel albero). Poiché "alcuni algoritmi" sono identici ai parametri modificati, uno potrebbe iniettare tramite il costruttore in questo modo:

[Algo1(n_states=16), Algo1(n_states=8), Algo2(), ..]

Se vuoi fare un nuovo corso, puoi farlo

from functools import partial
Algo1a = partial(Algo1, n_states=16)

È difficile dare suggerimenti sul problema con codice comune senza ulteriori conoscenze sui tuoi particolari algoritmi.

Per il particolare problema in cui la pre-elaborazione è comune per quasi tutti gli algoritmi, non ho ancora una risposta diretta: dipende un po 'dal tuo codice. Ma ogni classe dovrebbe avere la responsabilità di scegliere come eseguire la preelaborazione (principio di responsabilità singola), sebbene una DataFactory sia efficace.

Non vorrei rovinare un codice logico per guadagnare velocità non ricalcolando il pre-processo, ma di nuovo non conosco il tuo particolare problema.

    
risposta data 09.09.2013 - 23:21
fonte

Leggi altre domande sui tag