【问题标题】:Counting bigrams real fast (with or without multiprocessing) - python快速计算二元组(有或没有多处理) - python
【发布时间】:2017-03-15 09:06:59
【问题描述】:

鉴于norvig.com/big.txt 中的big.txt,目标是非常快速地计算二元组(想象一下,我必须重复这个计数 100,000 次)。

根据Fast/Optimize N-gram implementations in python,像这样提取二元组是最理想的:

_bigrams = zip(*[text[i:] for i in range(2)])

如果我使用Python3,生成器将不会被评估,直到我使用list(_bigrams) 或其他一些会执行相同操作的函数实现它。

import io
from collections import Counter

import time
with io.open('big.txt', 'r', encoding='utf8') as fin:
     text = fin.read().lower().replace(u' ', u"\uE000")

while True: 
    _bigrams = zip(*[text[i:] for i in range(2)])
    start = time.time()
    top100 = Counter(_bigrams).most_common(100)
    # Do some manipulation to text and repeat the counting.
    text = manipulate(text, top100)      

但是每次迭代大约需要 1 秒以上的时间,而 100,000 次迭代太长了。

我也尝试过sklearn CountVectorizer,但提取、计数和获取前 100 个二元组的时间与原生 python 相当。

然后我尝试了一些multiprocessing,使用了Python multiprocessing and a shared counterhttp://eli.thegreenplace.net/2012/01/04/shared-counter-with-pythons-multiprocessing的轻微修改:

from multiprocessing import Process, Manager, Lock

import time

class MultiProcCounter(object):
    def __init__(self):
        self.dictionary = Manager().dict()
        self.lock = Lock()

    def increment(self, item):
        with self.lock:
            self.dictionary[item] = self.dictionary.get(item, 0) + 1

def func(counter, item):
    counter.increment(item)

def multiproc_count(inputs):
    counter = MultiProcCounter()
    procs = [Process(target=func, args=(counter,_in)) for _in in inputs]
    for p in procs: p.start()
    for p in procs: p.join()
    return counter.dictionary

inputs = [1,1,1,1,2,2,3,4,4,5,2,2,3,1,2]

print (multiproc_count(inputs))

但在二元计数中使用MultiProcCounter 每次迭代需要的时间甚至超过 1 秒。我不知道为什么会这样,使用int 的虚拟列表示例,multiproc_count 可以完美运行。

我试过了:

import io
from collections import Counter

import time
with io.open('big.txt', 'r', encoding='utf8') as fin:
     text = fin.read().lower().replace(u' ', u"\uE000")

while True:
    _bigrams = zip(*[text[i:] for i in range(2)])
    start = time.time()
    top100 = Counter(multiproc_count(_bigrams)).most_common(100)

有没有办法在 Python 中真正快速地计算二元组?

【问题讨论】:

  • 如果您确实无法避免执行相同的操作 100,000 次,那么您应该研究分布式处理和 map/reduce。我假设您的意思是您拥有更大的数据,而不是您实际上重复相同的计算 100,000 次;如果这真的是你的意思,那听起来你的基本计划有缺陷。
  • 它重复相同的事情 100,000 次,但每次都需要前 100 个二元组并操作文本,因此每次迭代时提取二元组的输入文本都不同。
  • 你认为big.txt的前20个二元组是['th', 'he', 'e', 'p', 'pr', 'ro', 'oj', 'je'、'ec'、'ct'、't'、'g'、'gu'、'ut'、'te'、'en'、'nb'、'be'、'er'、'rg '] 作为您的代码生成的,或面向单词的子集,例如 ['th', 'he', 'pr', 'ro', 'oj', 'je', 'ec', 'ct', 'gu' , 'ut', 'te', 'en', 'nb', 'be', 'er', 'rg', 'eb', 'bo', 'oo', 'ok']?只是想了解游戏规则。
  • 编辑是否如此复杂以至于您无法在更改文本时更新二元计数?例如。当您将o → a 替换为dog 时,您可以减少doog 并增加daag。如果大部分文本没有变化,这应该比重复计算要快。
  • 是的,更新二元组的计数是可能的,但这意味着我需要一个 n^2 的哈希表,因为 n 是没有的。字符数,在某些情况下,n=300,000 =(

标签: python optimization mapreduce counter n-gram


【解决方案1】:
import os, thread

text = 'I really like cheese' #just load whatever you want here, this is just an example

CORE_NUMBER = os.cpu_count() # may not be available, just replace with how many cores you have if it crashes

ready = []
bigrams = []

def extract_bigrams(cores):
    global ready, bigrams
    bigrams = []
    ready = []
    for a in xrange(cores): #xrange is best for performance
        bigrams.append(0)
        ready.append(0)
    cpnt = 0#current point
    iterator = int(len(text)/cores)
    for a in xrange(cores-1):
        thread.start_new(extract_bigrams2, (cpnt, cpnt+iterator+1, a)) #overlap is intentional
        cpnt += iterator
    thread.start_new(extract_bigrams2, (cpnt, len(text), a+1))
    while 0 in ready:
        pass

def extract_bigrams2(startpoint, endpoint, threadnum):
    global ready, bigrams
    ready[threadnum] = 0
    bigrams[threadnum] = zip(*[text[startpoint+i:endpoint] for i in xrange(2)])
    ready[threadnum] = 1

extract_bigrams(CORE_NUMBER)
thebigrams = []
for a in bigrams:
    thebigrams+=a

print thebigrams

这个程序有一些问题,比如它没有过滤掉空格或标点符号,但我制作这个程序是为了展示你应该拍摄的内容。您可以轻松地对其进行编辑以满足您的需要。

此程序会自动检测您的计算机有多少个内核,并创建该数量的线程,尝试平均分配它查找二元组的区域。我只能在学校拥有的计算机上的在线浏览器中测试此代码,所以我不能确定它是否完全有效。如果有任何问题或疑问,请留在 cmets 中。

【讨论】:

  • 我很欣赏您的线程化方法——数据过滤和大小写折叠等事情发生在线程化之前,因此不会显着影响性能增益。但是,您的解决方案实际上并没有计算二元组——一旦线程完成,主程序将不得不合并所有计数,这是单线程解决方案不会面临的复杂情况。如果没有更完整的示例,很难知道额外的开销是否会抵消潜在收益。
  • 您是否尝试使用 big.txt 与原生 python 非线程/非多处理方法进行基准测试?
【解决方案2】:

我的建议:

Text= "The Project Gutenberg EBook of The Adventures of Sherlock Holmes"
"by Sir Arthur Conan Doyle"

# Counters
Counts= [[0 for x in range(128)] for y in range(128)]

# Perform the counting
R= ord(Text[0])
for i in range(1, len(Text)):
    L= R; R= ord(Text[i])
    Counts[L][R]+= 1

# Output the results
for i in range(ord('A'), ord('{')):
    if i < ord('[') or i >= ord('a'):
        for j in range(ord('A'), ord('{')):
            if (j < ord('[') or j >= ord('a')) and Counts[i][j] > 0:
                print chr(i) + chr(j), Counts[i][j]


Ad 1
Bo 1
EB 1
Gu 1
Ho 1
Pr 1
Sh 1
Th 2
be 1
ck 1
ct 1
dv 1
ec 1
en 2
er 2
es 2
he 3
je 1
lm 1
lo 1
me 1
nb 1
nt 1
oc 1
of 2
oj 1
ok 1
ol 1
oo 1
re 1
rg 1
rl 1
ro 1
te 1
tu 1
ur 1
ut 1
ve 1

此版本区分大小写;可能最好先将整个文本小写。

【讨论】:

  • 假设没有。字符数是固定的,不是吗?
  • @alvas: 多少个字符?
  • 我的意思是Counts= [[0 for x in range(128)] for y in range(128)]的固定数组列表。
  • @alvas:是的,数组比字典快。
  • 我不明白你的说法。在我的系统上,您的基于数组的解决方案比 OP 的基于字典的解决方案慢,并且比包含与您的所有相同数据过滤和大小写折叠的基于字典的解决方案慢。我错过了什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-05
  • 2012-09-21
  • 2015-10-15
  • 1970-01-01
  • 2020-09-01
  • 2012-03-18
  • 1970-01-01
相关资源
最近更新 更多