2013-10-24 11 views
25

sto cercando di rimuovere le parole non significative da una stringa di testo:modo più veloce per rimuovere parole di arresto in Python

from nltk.corpus import stopwords 
text = 'hello bye the the hi' 
text = ' '.join([word for word in text.split() if word not in (stopwords.words('english'))]) 

sto elaborando 6 mil di tali stringhe per cui la velocità è importante. Profiling il mio codice, la parte più lenta sono le righe sopra, c'è un modo migliore per farlo? Sto pensando di usare qualcosa come regex re.sub ma non so come scrivere il modello per un insieme di parole. Qualcuno può darmi una mano e sono anche felice di sentire altri metodi forse più veloci.

Nota: ho provato a suggerire qualcuno di avvolgere stopwords.words('english') con set() ma ciò non ha fatto alcuna differenza.

Grazie.

+0

Quanto è grande 'stopwords.words ('english')'? –

+0

@SteveBarnes Un elenco di 127 parole – mchangun

+2

hai avvolgerla all'interno di lista o fuori? provo aggiungere stw_set = set (stopwords.words ('inglese')) e utilizzare questo oggetto invece – alko

risposta

63

Provare a memorizzare nella cache l'oggetto stopword, come mostrato di seguito. Costruire questo ogni volta che si chiama la funzione sembra essere il collo di bottiglia.

from nltk.corpus import stopwords 

    cachedStopWords = stopwords.words("english") 

    def testFuncOld(): 
     text = 'hello bye the the hi' 
     text = ' '.join([word for word in text.split() if word not in stopwords.words("english")]) 

    def testFuncNew(): 
     text = 'hello bye the the hi' 
     text = ' '.join([word for word in text.split() if word not in cachedStopWords]) 

    if __name__ == "__main__": 
     for i in xrange(10000): 
      testFuncOld() 
      testFuncNew() 

ho eseguito questo attraverso il profiler: python -m Cprofile -s test.py cumulativa. Le linee rilevanti sono pubblicate di seguito.

nCalls tempo cumulativo

10000 7,723 words.py:7(testFuncOld)

10000 0,140 words.py:11(testFuncNew)

Così, la memorizzazione nella cache l'istanza stopwords dà un aumento di velocità 70x ~ .

+0

concordato. I miglioramenti delle prestazioni provengono dalla memorizzazione nella cache delle parole chiave, non proprio dalla creazione di un 'set'. – mchangun

+4

Certamente si ottiene una spinta drammatica da non dover leggere la lista dal disco ogni volta, perché questa è l'operazione più in termini di tempo. Ma se ora trasformi la tua lista "memorizzata nella cache" in un set (solo una volta, ovviamente), otterrai un'altra spinta. – alexis

+0

qualcuno può dirmi se questo supporta il giapponese? –

4

In primo luogo, si creano parole di arresto per ogni stringa. Crealo una volta. Set sarebbe davvero fantastico qui.

forbidden_words = set(stopwords.words('english')) 

Più tardi, sbarazzarsi di [] all'interno join. Usa invece il generatore.

' '.join([x for x in ['a', 'b', 'c']]) 

sostituire a

' '.join(x for x in ['a', 'b', 'c']) 

cosa successiva da affrontare sarebbe quello di rendere .split() valori di rendimento invece di restituire una matrice. Credo che regex sarebbe una buona sostituzione qui. Vedere thist hread perché il s.split() è in realtà veloce.

Infine, eseguire un tale lavoro in parallelo (rimuovendo le parole di arresto in stringhe da 6 m). Questo è un argomento completamente diverso.

+1

Dubito che l'uso di espressioni regolari sarà un miglioramento, vedere http: //stackoverflow.com/questions/7501609/python-re-split-vs-split/7501659#7501659 – alko

+0

trovato solo ora pure. :) –

+0

Grazie. Il 'set' ha fatto almeno un miglioramento di 8 volte per velocizzare. Perché usare un generatore di aiuto? La RAM non è un problema per me perché ogni pezzo di testo è piuttosto piccolo, circa 100-200 parole. – mchangun

8

Utilizzare un regexp per rimuovere tutte le parole che non corrispondono:

import re 
pattern = re.compile(r'\b(' + r'|'.join(stopwords.words('english')) + r')\b\s*') 
text = pattern.sub('', text) 

Questo sarà probabilmente modo più veloce di loop da soli, soprattutto per le grandi stringhe di input.

Se l'ultima parola nel testo viene eliminata da questo, è possibile che vi sia uno spazio vuoto finale. Propongo di gestirlo separatamente.

+0

Qualche idea di quale sarebbe la complessità di questo? Se w = numero di parole nel mio testo e s = numero di parole nella lista di stop, penso che il ciclo sarebbe nell'ordine di 'w log s'. In questo caso, w è approssimativamente s quindi è "w w log". Non sarebbe più lento dal momento che (approssimativamente) deve corrispondere carattere per carattere? – mchangun

+2

In realtà penso che le complessità nel significato di O (...) siano le stesse. Entrambi sono 'O (w log s)', sì. ** MA I regex sono implementati su un livello molto più basso e ottimizzati pesantemente. Già la divisione delle parole porterà a copiare tutto, creando un elenco di stringhe e la lista stessa, tutto ciò richiede tempo prezioso. – Alfe