2014-12-16 1 views
37

Ho fatto alcune osservazioni strane che i miei GridSearches continuano a mancare dopo un paio d'ore e inizialmente non riuscivo a capire perché. Ho monitorato l'utilizzo della memoria poi nel tempo e ho visto che era iniziato con qualche gigabyte (~ 6 Gb) e continuato ad aumentare fino a quando non si è schiantato sul nodo quando ha raggiunto il massimo. 128 GB l'hardware può prendere. Stavo sperimentando foreste casuali per la classificazione di un gran numero di documenti di testo. Per semplicità - per capire cosa sta succedendo - sono tornato a Naive Bayes.GridSearch e Python di scikit in generale non liberano memoria

Le versioni sto usando sono

  • Python 3.4.2
  • scikit-learn 0.15.2

ho trovato un po 'di spiegazioni dettagliate sulla lista scikit-problema su GitHub su questo argomento: https://github.com/scikit-learn/scikit-learn/issues/565 e https://github.com/scikit-learn/scikit-learn/pull/770

E sembra che sia già stato risolto con successo!

Così, il relativo codice che sto usando è

grid_search = GridSearchCV(pipeline, 
          parameters, 
          n_jobs=1, # 
          cv=5, 
          scoring='roc_auc', 
          verbose=2, 
          pre_dispatch='2*n_jobs', 
          refit=False) # tried both True and False 

grid_search.fit(X_train, y_train) 
print('Best score: {0}'.format(grid_search.best_score_)) 
print('Best parameters set:') 

Solo per curiosità, ho poi deciso di fare ricerca la griglia del & modo veloce sporco via nidificato ciclo for

for p1 in parameterset1: 
    for p2 in parameterset2: 
     ... 
      pipeline = Pipeline([ 
         ('vec', CountVectorizer(
            binary=True, 
            tokenizer=params_dict[i][0][0], 
            max_df=params_dict[i][0][1], 
            max_features=params_dict[i][0][2], 
            stop_words=params_dict[i][0][3], 
            ngram_range=params_dict[i][0][4],)), 
         ('tfidf', TfidfTransformer(
             norm=params_dict[i][0][5], 
             use_idf=params_dict[i][0][6], 
             sublinear_tf=params_dict[i][0][7],)), 
         ('clf', MultinomialNB())]) 

      scores = cross_validation.cross_val_score(
             estimator=pipeline, 
             X=X_train, 
             y=y_train, 
             cv=5, 
             scoring='roc_auc', 
             n_jobs=1) 

      params_dict[i][1] = '%s,%0.4f,%0.4f' % (params_dict[i][1], scores.mean(), scores.std()) 
      sys.stdout.write(params_dict[i][1] + '\n') 

Fin qui tutto bene. La ricerca della griglia viene eseguita e scrive i risultati sullo stdout. Tuttavia, dopo un po 'di tempo supera nuovamente il limite di memoria di 128 Gb. Lo stesso problema con GridSearch in scikit. Dopo alcuni esperimenti, ho finalmente scoperto che

gc.collect() 
len(gc.get_objects()) # particularly this part! 

nel ciclo for risolve il problema e l'utilizzo della memoria rimane costante al 6,5 Gb nel tempo di esecuzione di ~ 10 ore.

Alla fine, ho avuto modo di lavorare con la soluzione di cui sopra, tuttavia, sono curioso di sentire le tue idee su ciò che potrebbe causare questo problema e i tuoi suggerimenti & suggerimenti!

+1

Questo è estremamente strano. Potresti per favore presentare un nuovo problema su github includendo uno script che riproduce i problemi usando dati generati casualmente (o anche dati costanti, ad esempio 'np.ones (shape = (n_samples, n_features), dtype = np.float)')? – ogrisel

+7

Certo, nessun problema. Ho caricato un codice che ha causato questo problema a https://github.com/rasbt/bugreport/tree/master/scikit-learn/gridsearch_memory e ho aperto un problema qui: https://github.com/scikit-learn/scikit- imparare/temi/3973. Grazie! – Sebastian

+0

In passato, ho anche scoperto che alcune cose in sklearn (di solito con una foresta casuale) consumano troppa memoria. A seconda del problema, ho dovuto aggirare il problema. Un commento è che per i problemi tfidf/document un GradientBoostingClassifier può dare risultati migliori di un RandomForest.Inoltre, sono abbastanza sicuro che il trasformatore tfidf restituirà una matrice sparsa (fare questo per la tua versione) ... quindi devi aggiornare il tuo sklearn perché RandomForest in 0.15.2 non supporta input sparsi. – user1269942

risposta

1

RandomForest in 0.15.2 non supporta input sparse.

Aggiornamento sklearn e riprovare ... speriamo che questo consentirà alle copie multiple che finiscono di essere utilizzate per consumare meno memoria. (e velocizza le cose)

+0

Grazie per il suggerimento. Sì, stavo usando la chiamata toarray() per trasformare gli array sparsi in array densi che sono sicuramente molto costosi. Tuttavia, questo problema si è verificato anche con un Bayes ingenuo "economico" o un classificatore di regressione logistica (Sgd). In qualche modo c'era un problema con la garbage collection. Dovrei riprovarci qualche volta con Python 3.4.3 e scikit-learn versione 0.16.1 – Sebastian

+0

Credo che NB e LR supportino anche input sparsi. potresti provare a cambiare pre_dispatch = '2 * n_jobs' con un valore di 1 – user1269942

0

Non riesco a vedere il tuo codice esatto, ma ho affrontato problemi simili al giorno d'oggi. Vale la pena provare. La simile esplosione di memoria potrebbe facilmente accadere quando copiamo valori da una matrice mutabile o elenco come oggetto ad un'altra variabile creando una copia di quella originale e poi modifichiamo la nuova matrice o lista con append o qualcosa di simile aumentando la dimensione di esso e allo stesso tempo aumenta anche l'oggetto originale sullo sfondo.

Quindi questo è un processo esponenziale, quindi dopo qualche tempo abbiamo esaurito la memoria. Sono stato in grado di e forse puoi evitare questo tipo di fenomeno con deepcopy() l'oggetto originale ad un valore che passa.

Ho avuto il problema simile, ho fatto esplodere la memoria con un processo simile, quindi sono riuscito a rimanere al 10% di carico della memoria.

UPDATE: Ora vedo lo snippet del codice con DataFrame panda. Ci sarebbe un tale problema di valutazione facilmente.

+0

Devo dare di nuovo un'occhiata al codebase di scikit-learn, ma sono abbastanza sicuro che ci siano un sacco di copie internamente (negli stimatori) andando avanti per motivi di sicurezza. Comunque, a un certo punto dovrebbero essere "garbage collection & disposed" e quando ho usato il 'gc.collect() len (gc.get_objects())' ha funzionato magicamente. Devo provare un po 'di tempo in futuro per vedere se era specifico dell'architettura o della versione Python – Sebastian

+0

, proverei almeno a passare il valore pf dei df, ma comunque buona fortuna. – Geeocode

0

Non ho familiarità con GridSearch signore, ma suggerirei che quando la memoria e le liste enormi sono un problema, scrivi un piccolo generatore personalizzato. Può essere riutilizzato per tutti i tuoi articoli, basta usarne uno che prende qualsiasi lista. Se l'implementazione oltre la soluzione inferiore qui in primo luogo leggere questo articolo, miglior articolo generatore che ho trovato. Ho scritto tutto e andato pezzo per pezzo, tutte le vostre domande dopo averlo letto mi può provare troppo

https://www.jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/

non hanno bisogno: for p1 in parameterset1:

Prova

def listerator(this_list): 
    i = 0 
    while True: 
     yield this_list[i] 
     i += 1 

La parola 'yield' (ovunque nella dichiarazione) rende questo un generatore, non una funzione regolare. Questo scorre e dice che sono uguale a 0, mentre Vero che devo fare cose, vogliono che io ceda questa_list [0], qui vai Ti aspetterò allo i += 1 se ti serve ancora. La prossima volta che viene chiamato, prende e fa i += 1, e nota che è ancora in un ciclo while e dà this_list [1], e registra la sua posizione (i += 1 di nuovo ... vi aspetterà fino a quando chiamato di nuovo). Notate mentre lo alimento la lista una volta e faccio un generatore (x qui), esaurirà la vostra lista.

In [141]: x = listerator([1,2,3]) 

In [142]: next(x) 
Out[142]: 1 

In [143]: next(x) 
Out[143]: 2 

In [144]: next(x) 
Out[144]: 3 

In [148]: next(x) 
--------------------------------------------------------------------------- 
IndexError        Traceback (most recent call last) 
<ipython-input-148-5e4e57af3a97> in <module>() 
----> 1 next(x) 

<ipython-input-139-ed3d6d61a17c> in listerator(this_list) 
     2  i = 0 
     3  while True: 
----> 4    yield this_list[i] 
     5    i += 1 
     6 

IndexError: list index out of range 

Vediamo se siamo in grado di utilizzarlo in un per:

In [221]: for val in listerator([1,2,3,4]): 
    .....:  print val 
    .....:  
1 
2 
3 
4 
--------------------------------------------------------------------------- 
IndexError        Traceback (most recent call last) 
<ipython-input-221-fa4f59138165> in <module>() 
----> 1 for val in listerator([1,2,3,4]): 
     2  print val 
     3 

<ipython-input-220-263fba1d810b> in listerator(this_list, seed) 
     2   i = seed or 0 
     3   while True: 
----> 4    yield this_list[i] 
     5    i += 1 

IndexError: list index out of range 

Nope. Proviamo a gestirlo:

def listerator(this_list): 
    i = 0 
    while True: 
     try: 
      yield this_list[i] 
     except IndexError: 
      break 
     i += 1 

In [223]: for val in listerator([1,2,3,4]): 
    print val 
    .....:  
1 
2 
3 
4 

Che funziona. Ora non cercherà ciecamente di restituire un elemento di lista anche se non è lì. Da quello che hai detto, posso quasi garantire avrete bisogno di essere in grado di seminare esso (pick up da un certo luogo, o iniziare appena da un determinato luogo):

def listerator(this_list, seed=None): 
    i = seed or 0 
    while True: 
     try: 
      yield this_list[i] 
     except IndexError: 
      break 
     i += 1 

In [150]: l = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] 

In [151]: x = listerator(l, 8) 

In [152]: next(x) 
Out[152]: 9 

In [153]: next(x) 
Out[153]: 10 

In [154]: next(x) 
Out[154]: 11 

i = seed or 0 è una cosa che cerca seed, ma seed ha come valore predefinito None, quindi di solito inizia solo nel punto logico, 0, l'inizio della lista

Come si può usare questa bestia senza usare (quasi) alcuna memoria?

parameterset1 = [1,2,3,4] 
parameterset2 = ['a','b','c','d'] 

In [224]: for p1 in listerator(parameterset1): 
    for p2 in listerator(parameterset2): 
     print p1, p2 
    .....:   
1 a 
1 b 
1 c 
1 d 
2 a 
2 b 
2 c 
2 d 
3 a 
3 b 
3 c 
3 d 
4 a 
4 b 
4 c 
4 d 

che sembra familiare eh? Ora puoi elaborare un trilione di valori uno per uno, scegliere quelli importanti da scrivere su disco e non far saltare in aria il tuo sistema. Godere!

+0

Ciao, grazie per il commento, so come usare i generatori, tuttavia questo era più su un potenziale bug specifico della piattaforma che volevo discutere. Certo, potresti usare un HashingVectorizer e un classificatore SGD per lo streaming dei dati; ma per Tf-idfs per essere calcolato correttamente, è necessario avere accesso all'intero vocabolario – Sebastian

+0

Ya, ho notato dopo averlo finito probabilmente non sarebbe d'aiuto, devo leggere la domanda più lentamente, ma contento che l'abbia risolto – codyc4321