wake-up-neo.net

Word-Frequenzen in Python effizient zählen

Ich möchte die Häufigkeit aller Wörter in einer Textdatei zählen.

>>> countInFile('test.txt')

sollte {'aaa':1, 'bbb': 2, 'ccc':1} zurückgeben, wenn die Zieltextdatei folgendermaßen aussieht:

# test.txt
aaa bbb ccc
bbb

Ich habe es mit reinem Python nach einigen Beiträgen implementiert. Ich habe jedoch herausgefunden, dass reine Python-Methoden aufgrund der großen Dateigröße (> 1 GB) nicht ausreichen.

Ich denke, die Machtübernahme von sklearn ist ein Kandidat.

Wenn Sie CountVectorizer Frequenzen für jede Zeile zählen lassen, werden Sie wahrscheinlich Word-Frequenzen erhalten, indem Sie jede Spalte summieren. Aber es klingt ein bisschen indirekt.

Was ist der effizienteste und einfachste Weg, Wörter in einer Datei mit Python zu zählen?

Aktualisieren

Mein (sehr langsamer) Code ist hier:

from collections import Counter

def get_term_frequency_in_file(source_file_path):
    wordcount = {}
    with open(source_file_path) as f:
        for line in f:
            line = line.lower().translate(None, string.punctuation)
            this_wordcount = Counter(line.split())
            wordcount = add_merge_two_dict(wordcount, this_wordcount)
    return wordcount

def add_merge_two_dict(x, y):
    return { k: x.get(k, 0) + y.get(k, 0) for k in set(x) | set(y) }
27
rkjt50r983

Der prägnanteste Ansatz ist die Verwendung der Tools, die Python Ihnen zur Verfügung stellt.

from future_builtins import map  # Only on Python 2

from collections import Counter
from itertools import chain

def countInFile(filename):
    with open(filename) as f:
        return Counter(chain.from_iterable(map(str.split, f)))

Das ist es. map(str.split, f) erstellt einen Generator, der lists von Wörtern aus jeder Zeile zurückgibt. Durch das Wrapping in chain.from_iterable wird das in einen einzelnen Generator konvertiert, der jeweils ein Word erzeugt. Counter nimmt eine Eingabe auf und zählt alle darin enthaltenen eindeutigen Werte. Am Ende return ein dict-ähnliches Objekt (eine Counter), das alle eindeutigen Wörter und deren Anzahl speichert, und während der Erstellung nur eine Datenzeile und die Gesamtzahl und nicht die gesamte Datei gleichzeitig gespeichert werden.

Theoretisch könnten Sie unter Python 2.7 und 3.1 die verketteten Ergebnisse etwas besser durchlaufen und eine dict oder collections.defaultdict(int) zum Zählen verwenden (da Counter in Python implementiert ist, was in manchen Fällen dazu führen kann, dass sie langsamer wird), aber Counter Die Arbeit ist einfacher und selbstdokumentierender (ich meine, das ganze Ziel zählt, verwenden Sie also eine Counter). Darüber hinaus verfügt Counter auf CPython (dem Referenzinterpreter) ab Version 3.2 über einen Beschleuniger auf C-Ebene, um die iterierbaren Eingänge zu zählen, die schneller ausgeführt werden als alles, was Sie in reinem Python schreiben könnten.

Update: Sie scheinen die Interpunktion entfernt zu haben und die Groß- und Kleinschreibung zu berücksichtigen, daher ist hier eine Variante meines früheren Codes, der dies tut:

from string import punctuation

def countInFile(filename):
    with open(filename) as f:
        linewords = (line.translate(None, punctuation).lower().split() for line in f)
        return Counter(chain.from_iterable(linewords))

Ihr Code läuft viel langsamer, da er viele kleine Counter- und set-Objekte erstellt und zerstört, anstatt .update eine einzelne Counter einmal pro Zeile zu erzeugen (was etwas langsamer ist als das, was ich im aktualisierten Codeblock gegeben habe, zumindest algorithmisch wäre.) ähnlich im Skalierungsfaktor).

35
ShadowRanger

Ein speichereffizienter und präziser Weg ist die Nutzung 

  • CountVectorizer in scikit (für die Ngram-Extraktion)
  • NLTK für Word_tokenize
  • numpy Matrixsumme zum Erfassen der Zählungen
  • collections.Counter zum Erfassen der Zählungen und des Wortschatzes

Ein Beispiel:

import urllib.request
from collections import Counter

import numpy as np 

from nltk import Word_tokenize
from sklearn.feature_extraction.text import CountVectorizer

# Our sample textfile.
url = 'https://raw.githubusercontent.com/Simdiva/DSL-Task/master/data/DSLCC-v2.0/test/test.txt'
response = urllib.request.urlopen(url)
data = response.read().decode('utf8')


# Note that `ngram_range=(1, 1)` means we want to extract Unigrams, i.e. tokens.
ngram_vectorizer = CountVectorizer(analyzer='Word', tokenizer=Word_tokenize, ngram_range=(1, 1), min_df=1)
# X matrix where the row represents sentences and column is our one-hot vector for each token in our vocabulary
X = ngram_vectorizer.fit_transform(data.split('\n'))

# Vocabulary
vocab = list(ngram_vectorizer.get_feature_names())

# Column-wise sum of the X matrix.
# It's some crazy numpy syntax that looks horribly unpythonic
# For details, see http://stackoverflow.com/questions/3337301/numpy-matrix-to-array
# and http://stackoverflow.com/questions/13567345/how-to-calculate-the-sum-of-all-columns-of-a-2d-numpy-array-efficiently
counts = X.sum(axis=0).A1

freq_distribution = Counter(dict(Zip(vocab, counts)))
print (freq_distribution.most_common(10))

[aus]:

[(',', 32000),
 ('.', 17783),
 ('de', 11225),
 ('a', 7197),
 ('que', 5710),
 ('la', 4732),
 ('je', 4304),
 ('se', 4013),
 ('на', 3978),
 ('na', 3834)]

Im Wesentlichen können Sie dies auch tun:

from collections import Counter
import numpy as np 
from nltk import Word_tokenize
from sklearn.feature_extraction.text import CountVectorizer

def freq_dist(data):
    """
    :param data: A string with sentences separated by '\n'
    :type data: str
    """
    ngram_vectorizer = CountVectorizer(analyzer='Word', tokenizer=Word_tokenize, ngram_range=(1, 1), min_df=1)
    X = ngram_vectorizer.fit_transform(data.split('\n'))
    vocab = list(ngram_vectorizer.get_feature_names())
    counts = X.sum(axis=0).A1
    return Counter(dict(Zip(vocab, counts)))

Lassen Sie uns timeit:

import time

start = time.time()
Word_distribution = freq_dist(data)
print (time.time() - start)

[aus]:

5.257147789001465

Beachten Sie, dass CountVectorizer anstelle eines Strings auch eine Datei verwenden kann und thier nicht die gesamte Datei in den Speicher lesen muss. In Code:

import io
from collections import Counter

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer

infile = '/path/to/input.txt'

ngram_vectorizer = CountVectorizer(analyzer='Word', ngram_range=(1, 1), min_df=1)

with io.open(infile, 'r', encoding='utf8') as fin:
    X = ngram_vectorizer.fit_transform(fin)
    vocab = ngram_vectorizer.get_feature_names()
    counts = X.sum(axis=0).A1
    freq_distribution = Counter(dict(Zip(vocab, counts)))
    print (freq_distribution.most_common(10))
8
alvas

Hier ist ein Benchmark. Es wird seltsam aussehen, aber der gröbste Code gewinnt.

[Code]:

from collections import Counter, defaultdict
import io, time

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer

infile = '/path/to/file'

def extract_dictionary_sklearn(file_path):
    with io.open(file_path, 'r', encoding='utf8') as fin:
        ngram_vectorizer = CountVectorizer(analyzer='Word')
        X = ngram_vectorizer.fit_transform(fin)
        vocab = ngram_vectorizer.get_feature_names()
        counts = X.sum(axis=0).A1
    return Counter(dict(Zip(vocab, counts)))

def extract_dictionary_native(file_path):
    dictionary = Counter()
    with io.open(file_path, 'r', encoding='utf8') as fin:
        for line in fin:
            dictionary.update(line.split())
    return dictionary

def extract_dictionary_Paddle(file_path):
    dictionary = defaultdict(int)
    with io.open(file_path, 'r', encoding='utf8') as fin:
        for line in fin:
            for words in line.split():
                dictionary[Word] +=1
    return dictionary

start = time.time()
extract_dictionary_sklearn(infile)
print time.time() - start

start = time.time()
extract_dictionary_native(infile)
print time.time() - start

start = time.time()
extract_dictionary_Paddle(infile)
print time.time() - start

[aus]:

38.306814909
24.8241138458
12.1182529926

Datengröße (154 MB), die im obigen Benchmark verwendet wird:

$ wc -c /path/to/file
161680851

$ wc -l /path/to/file
2176141

Einige Dinge zu beachten:

  • In der Version sklearn ist die Erstellung von Vectorizer + die Bearbeitung und die Umwandlung in ein Counter-Objekt mit einem Mehraufwand verbunden
  • Die native Counter-Updateversion scheint Counter.update() eine teure Operation zu sein
3
nat gillin

Das sollte ausreichen.

def countinfile(filename):
    d = {}
    with open(filename, "r") as fin:
        for line in fin:
            words = line.strip().split()
            for Word in words:
                try:
                    d[Word] += 1
                except KeyError:
                    d[Word] = 1
    return d
2
Goodies

du kannst es mit sklearn versuchen

from sklearn.feature_extraction.text import CountVectorizer
    vectorizer = CountVectorizer()

    data=['i am student','the student suffers a lot']
    transformed_data =vectorizer.fit_transform(data)
    vocab= {a: b for a, b in Zip(vectorizer.get_feature_names(), np.ravel(transformed_data.sum(axis=0)))}
    print (vocab)
0

Anstatt die gesamten aus der URL gelesenen Bytes zu decodieren, verarbeite ich die binären Daten. Da bytes.translate erwartet, dass sein zweites Argument eine Byte-Zeichenfolge ist, codiert utf-8 punctuation. Nach dem Entfernen von Satzzeichen decodiere ich den Byte-String. 

Die Funktion freq_dist erwartet eine Iteration. Deshalb habe ich data.splitlines() bestanden. 

from urllib2 import urlopen
from collections import Counter
from string import punctuation
from time import time
import sys
from pprint import pprint

url = 'https://raw.githubusercontent.com/Simdiva/DSL-Task/master/data/DSLCC-v2.0/test/test.txt'

data = urlopen(url).read()

def freq_dist(data):
    """
    :param data: file-like object opened in binary mode or
                 sequence of byte strings separated by '\n'
    :type data: an iterable sequence
    """
    #For readability   
    #return Counter(Word for line in data
    #    for Word in line.translate(
    #    None,bytes(punctuation.encode('utf-8'))).decode('utf-8').split())

    punc = punctuation.encode('utf-8')
    words = (Word for line in data for Word in line.translate(None, punc).decode('utf-8').split())
    return Counter(words)


start = time()
Word_dist = freq_dist(data.splitlines())
print('elapsed: {}'.format(time() - start))
pprint(Word_dist.most_common(10))

Ausgabe;

elapsed: 0.806480884552

[(u'de', 11106),
 (u'a', 6742),
 (u'que', 5701),
 (u'la', 4319),
 (u'je', 4260),
 (u'se', 3938),
 (u'\u043d\u0430', 3929),
 (u'na', 3623),
 (u'da', 3534),
 (u'i', 3487)]

Es scheint, dass dict effizienter ist als Counter-Objekt. 

def freq_dist(data):
    """
    :param data: A string with sentences separated by '\n'
    :type data: str
    """
    d = {}
    punc = punctuation.encode('utf-8')
    words = (Word for line in data for Word in line.translate(None, punc).decode('utf-8').split())
    for Word in words:
        d[Word] = d.get(Word, 0) + 1
    return d

start = time()
Word_dist = freq_dist(data.splitlines())
print('elapsed: {}'.format(time() - start))
pprint(sorted(Word_dist.items(), key=lambda x: (x[1], x[0]), reverse=True)[:10])

Ausgabe;

elapsed: 0.642680168152

[(u'de', 11106),
 (u'a', 6742),
 (u'que', 5701),
 (u'la', 4319),
 (u'je', 4260),
 (u'se', 3938),
 (u'\u043d\u0430', 3929),
 (u'na', 3623),
 (u'da', 3534),
 (u'i', 3487)]

Um beim Öffnen einer großen Datei mehr Speichereffizienz zu erzielen, müssen Sie nur die geöffnete URL übergeben. Das Timing beinhaltet jedoch auch die Downloadzeit für Dateien.

data = urlopen(url)
Word_dist = freq_dist(data)
0
Nizam Mohamed

Überspringen Sie CountVectorizer und scikit-learn.

Die Datei ist möglicherweise zu groß, um sie in den Arbeitsspeicher zu laden, aber ich bezweifle, dass das Python-Wörterbuch zu groß wird. Die einfachste Option für Sie ist, die große Datei in 10-20 kleinere Dateien aufzuteilen und Ihren Code so zu erweitern, dass die kleineren Dateien überlaufen werden.

0
Stephen Grimes