2013-10-24 5 views
25

Ich versuche, von Stoppwörtern einer Reihe von Text zu entfernen zu entfernen:Schneller Weg Stoppwörter 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'))]) 

Ich bin die Verarbeitung 6 mil solcher Strings so die Geschwindigkeit wichtig ist. Profiling mein Code, der langsamste Teil ist die Zeilen oben, gibt es eine bessere Möglichkeit, dies zu tun? Ich denke daran, etwas wie Regex re.sub zu verwenden, aber ich weiß nicht, wie man das Muster für eine Reihe von Wörtern schreibt. Kann mir jemand eine Hand geben und ich bin auch froh, andere möglicherweise schnellere Methoden zu hören.

Hinweis: Ich habe versucht, jemand schlägt vor, stopwords.words('english') mit set() zu wickeln, aber das machte keinen Unterschied.

Vielen Dank.

+0

Wie groß ist 'stopwords.words ('english')'? –

+0

@SteveBarnes Eine Liste von 127 Wörtern – mchangun

+2

hast du es in Listenverständnis oder außerhalb verpackt? try add stw_set = set (stopwords.words ('english')) und benutze stattdessen dieses Objekt – alko

Antwort

63

Versuchen Sie, das Stoppwörter-Objekt wie unten gezeigt zwischenzuspeichern. Dies ist bei jedem Aufruf der Funktion der Flaschenhals.

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() 

Ich laufe dies durch den Profiler: python -m cProfile es kumulativen test.py. Die relevanten Zeilen sind unten aufgeführt.

ncalls kumulative Zeit

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

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

also die Instanz Stoppwörter Cachen gibt eine ~ 70x Speedup .

+0

Einverstanden. Die Performance-Boosts kommt von Caching der Stoppwörter, nicht wirklich bei der Erstellung eines 'Set'. – mchangun

+4

Sicher bekommt man einen dramatischen Schub, weil man die Liste nicht jedes Mal von der Platte lesen muss, weil das die zeitaufwendigste Operation ist. Aber wenn Sie jetzt Ihre "Cache" -Liste in ein Set umwandeln (nur einmal natürlich), erhalten Sie einen weiteren Boost. – alexis

+0

kann mir jemand sagen ob das japanisch unterstützt? –

4

Zuerst erstellen Sie Stoppwörter für jede Zeichenfolge. Erstellen Sie es einmal. Set wäre in der Tat toll hier.

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

Später werde [] innen join befreien. Benutze stattdessen den Generator.

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

zu

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

Das nächste, was ersetzen wäre zu umgehen .split() Ertragswerte anstelle der Rücksendung eines Arrays zu machen. Ich glaube, regex wäre ein guter Ersatz hier. Siehe thist hread für warum s.split() ist eigentlich schnell.

Zuletzt, machen Sie einen solchen Job parallel (Entfernen von Stoppwörtern in 6m Strings). Das ist ein ganz anderes Thema.

+1

Ich bezweifle mit regexp werde eine Verbesserung sein, siehe http: //stackoverflow.com/questions/7501609/python-re-split-vs-split/7501659#7501659 – alko

+0

Gefunden gerade jetzt auch. :) –

+0

Danke. Das 'Set' machte mindestens eine 8fache Verbesserung der Geschwindigkeit. Warum hilft die Verwendung eines Generators? RAM ist kein Problem für mich, weil jedes Stück Text ziemlich klein ist, etwa 100-200 Wörter. – mchangun

8

ein regexp Verwenden Sie alle Wörter zu entfernen, die nicht zusammenpassen:

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

Dies ist wahrscheinlich Weise schneller als Looping selbst, vor allem für große Eingabezeichenfolgen sein.

Wenn das letzte Wort im Text dadurch gelöscht wird, haben Sie möglicherweise nachgestellte Leerzeichen. Ich schlage vor, dies getrennt zu behandeln.

+0

Irgendeine Idee, was die Komplexität wäre? Wenn w = Anzahl der Wörter in meinem Text und s = Anzahl der Wörter in der Stop-Liste, denke ich, dass die Schleife in der Größenordnung von "w log s" sein würde. In diesem Fall ist w ungefähr so, es ist "w log w". Wäre Grep nicht langsamer, da es (ungefähr) Zeichen für Zeichen entsprechen muss? – mchangun

+2

Eigentlich denke ich, dass die Komplexität in der Bedeutung von O (...) gleich ist. Beide sind "O (w log s)", ja. ** ABER ** Regexps sind auf einer viel niedrigeren Ebene implementiert und stark optimiert. Bereits das Teilen von Wörtern wird dazu führen, alles zu kopieren, eine Liste von Strings zu erstellen und die Liste selbst, alles, was kostbare Zeit kostet. – Alfe

Verwandte Themen