テキストファイル内のすべての単語の頻度をカウントしたいと思います。
>>> countInFile('test.txt')
ターゲットテキストファイルが次のような場合、{'aaa':1, 'bbb': 2, 'ccc':1}
を返します。
# test.txt
aaa bbb ccc
bbb
Pure python following some posts で実装しました。しかし、巨大なファイルサイズ(> 1GB)のためにpure-pythonの方法では不十分であることがわかりました。 。
Sklearnの力を借りることは候補だと思います。
CountVectorizerに各行の頻度をカウントさせると、各列を合計することでWordの頻度を取得できると思います。しかし、それは少し間接的な方法に聞こえます。
Pythonでファイル内の単語を数える最も効率的で簡単な方法は何ですか?
私の(非常に遅い)コードはここにあります:
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) }
最も簡潔なアプローチは、ツールPythonが提供します。
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)))
それでおしまい。 map(str.split, f)
は、各行からlist
s個の単語を返すジェネレーターを作成しています。 chain.from_iterable
でラップすると、一度にWordを生成する単一のジェネレーターに変換されます。 Counter
は反復可能な入力を受け取り、その中のすべての一意の値をカウントします。最後に、すべての一意の単語とそのカウントを保存するreturn
のようなオブジェクト(dict
)をCounter
します。作成時には、一度にファイル全体ではなく、一度に1行のデータと合計カウントのみを保存します。
理論的には、Python 2.7および3.1では、チェーンされた結果を自分で少しループし、dict
またはcollections.defaultdict(int)
を使用してカウントします(Counter
はPythonで実装されているため) 、場合によっては遅くなる可能性があります)、しかしCounter
に作業を任せることはより簡単で、より自己文書化されます(つまり、目標全体がカウントされるので、Counter
を使用します)。さらに、CPython(参照インタープリター)で3.2以降Counter
には、反復可能な入力をカウントするためのCレベルアクセラレータがあり、純粋なPythonで記述できるものよりも高速に実行されます。
更新:句読点を削除して大文字と小文字を区別しないようにしているので、これを行う以前のコードの変形を次に示します。
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))
1行に1つのCounter
を1回.update
- ingするのではなく、多くの小さなset
およびCounter
オブジェクトを作成および破棄しているため、コードの実行速度がはるかに遅くなりますスケーリング係数が似ています)。
メモリの効率的で正確な方法は、
scikit
のCountVectorizer(ngram抽出用)Word_tokenize
numpy
行列合計でカウントを収集しますcollections.Counter
カウントと語彙を収集するため例:
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))
[でる]:
[(',', 32000),
('.', 17783),
('de', 11225),
('a', 7197),
('que', 5710),
('la', 4732),
('je', 4304),
('se', 4013),
('на', 3978),
('na', 3834)]
基本的に、これも実行できます。
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)))
timeit
:
import time
start = time.time()
Word_distribution = freq_dist(data)
print (time.time() - start)
[でる]:
5.257147789001465
CountVectorizer
は文字列の代わりにファイルを取ることもでき、tはファイル全体をメモリに読み込む必要がないことに注意してください。コード内:
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))
ここにいくつかのベンチマークがあります。奇妙に見えますが、最も粗雑なコードが勝ちます。
[コード]:
_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
_
[でる]:
_38.306814909
24.8241138458
12.1182529926
_
上記のベンチマークで使用されるデータサイズ(154MB):
_$ wc -c /path/to/file
161680851
$ wc -l /path/to/file
2176141
_
注意するべき事柄:
sklearn
バージョンでは、ベクトライザー作成+ numpy操作およびCounter
オブジェクトへの変換のオーバーヘッドがありますCounter
アップデートバージョン、Counter.update()
は高価な操作のようですこれで十分です。
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
あなたはsklearnで試すことができます
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)
URLから読み取ったバイト全体をデコードする代わりに、バイナリデータを処理します。 _bytes.translate
_は2番目の引数がバイト文字列であると想定しているため、punctuation
をutf-8エンコードします。句読点を削除した後、バイト文字列をutf-8でデコードします。
関数_freq_dist
_は反復可能を想定しています。だからdata.splitlines()
を渡しました。
_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))
_
出力;
_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)]
_
dict
はCounter
オブジェクトよりも効率が良いようです。
_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])
_
出力;
_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)]
_
巨大なファイルを開くときのメモリ効率を高めるには、開いたURLだけを渡す必要があります。ただし、タイミングにはファイルのダウンロード時間も含まれます。
_data = urlopen(url)
Word_dist = freq_dist(data)
_
CountVectorizerとscikit-learnをスキップします。
ファイルが大きすぎてメモリにロードできない可能性がありますが、python辞書が大きすぎるとは思いません。大きなファイルを10〜20個の小さなファイルに分割して、小さなファイルをループするコード。