这段代码中的Python优化?

时间:2012-04-13 01:12:02

标签: python optimization

我有两个相当简单的代码片段,我正在运行它们很多次;我正在尝试确定是否可以进行任何优化以加快执行时间。如果有什么东西可以更快地完成......

在第一个中,我们有一个列表,字段。我们还有一个列表,权重列表。我们试图找出哪个权重列表乘以字段将产生最大总和。 Fields大约有30k条目。

def find_best(weights,fields):
  winner = -1
  best = -float('inf')
  for c in range(num_category):
    score = 0
    for i in range(num_fields):
      score += float(fields[i]) * weights[c][i]
    if score > best:
      best = score
      winner = c
  return winner

在第二个中,我们试图更新两个重量列表;一个增加,一个减少。增加/减少每个元素的数量等于字段中的对应元素(例如,如果字段[4] = 10.5,那么我们希望将权重[toincrease] [4]增加10.5并减少权重[todecrease] [4 ] 10.5)

 def update_weights(weights,fields,toincrease,todecrease):
   for i in range(num_fields):
     update = float(fields[i])
     weights[toincrease][i] += update
     weights[todecrease][i] -= update
   return weights

我希望这不是一个过于具体的问题。

6 个答案:

答案 0 :(得分:7)

当您尝试优化时,您 要做的事情是个人资料和衡量! Python提供了timeit模块,使测量变得简单!

这将假设您已事先将字段转换为浮点列表(在任何这些函数之外),因为字符串→浮点转换非常慢。您可以通过fields = [float(f) for f in string_fields]

执行此操作

另外,对于进行数值处理,纯python不是很好,因为它最终会为每个操作做很多类型检查(和其他一些东西)。使用像numpy这样的C库可以带来巨大的改进。

find_best

我已将其他人(以及其他人)的答案合并到一个分析套件中(比如test_find_best.py):

import random, operator, numpy as np, itertools, timeit

fields = [random.random() for _ in range(3000)]
fields_string = [str(field) for field in fields]
weights = [[random.random() for _ in range(3000)] for c in range(100)]

npw = np.array(weights)
npf = np.array(fields)   

num_fields = len(fields)
num_category = len(weights)

def f_original():
  winner = -1
  best = -float('inf')
  for c in range(num_category):
    score = 0
    for i in range(num_fields):
      score += float(fields_string[i]) * weights[c][i]
    if score > best:
      best = score
      winner = c

def f_original_no_string():
  winner = -1
  best = -float('inf')
  for c in range(num_category):
    score = 0
    for i in range(num_fields):
      score += fields[i] * weights[c][i]
    if score > best:
      best = score
      winner = c

def f_original_xrange():
  winner = -1
  best = -float('inf')
  for c in xrange(num_category):
    score = 0
    for i in xrange(num_fields):
      score += fields[i] * weights[c][i]
    if score > best:
      best = score
      winner = c


# Zenon  http://stackoverflow.com/a/10134298/1256624

def f_index_comprehension():
    winner = -1
    best = -float('inf')
    for c in range(num_category):
      score = sum(fields[i] * weights[c][i] for i in xrange(num_fields))
      if score > best:
        best = score
        winner = c  


# steveha  http://stackoverflow.com/a/10134247/1256624

def f_comprehension():
  winner = -1
  best = -float('inf')

  for c in xrange(num_category):
    score = sum(f * w for f, w in itertools.izip(fields, weights[c]))
    if score > best:
      best = score
      winner = c

def f_schwartz_original(): # https://en.wikipedia.org/wiki/Schwartzian_transform
    tup = max(((i, sum(t[0] * t[1] for t in itertools.izip(fields, wlist))) for i, wlist in enumerate(weights)),
              key=lambda t: t[1]
             )

def f_schwartz_opt(): # https://en.wikipedia.org/wiki/Schwartzian_transform
    tup = max(((i, sum(f * w for f,w in itertools.izip(fields, wlist))) for i, wlist in enumerate(weights)),
              key=operator.itemgetter(1)
             )

def fweight(field_float_list, wlist):
    f = iter(field_float_list)
    return sum(f.next() * w for w in wlist)

def f_schwartz_iterate():
     tup = max(
         ((i, fweight(fields, wlist)) for i, wlist in enumerate(weights)),
         key=lambda t: t[1]
      )

# Nolen Royalty  http://stackoverflow.com/a/10134147/1256624 

def f_numpy_mult_sum():
   np.argmax(np.sum(npf * npw, axis = 1))


# me

def f_imap():
  winner = -1
  best = -float('inf')

  for c in xrange(num_category):
    score = sum(itertools.imap(operator.mul, fields, weights[c]))
    if score > best:
      best = score
      winner = c

def f_numpy():
   np.argmax(npw.dot(npf))



for f in [f_original,
          f_index_comprehension,
          f_schwartz_iterate,
          f_original_no_string,
          f_schwartz_original,
          f_original_xrange,
          f_schwartz_opt,
          f_comprehension,
          f_imap]:
   print "%s: %.2f ms" % (f.__name__, timeit.timeit(f,number=10)/10 * 1000)
for f in [f_numpy_mult_sum, f_numpy]:
   print "%s: %.2f ms" % (f.__name__, timeit.timeit(f,number=100)/100 * 1000)

正在运行python test_find_best.py给我:

f_original: 310.34 ms
f_index_comprehension: 102.58 ms
f_schwartz_iterate: 103.39 ms
f_original_no_string: 96.36 ms
f_schwartz_original: 90.52 ms
f_original_xrange: 89.31 ms
f_schwartz_opt: 69.48 ms
f_comprehension: 68.87 ms
f_imap: 53.33 ms
f_numpy_mult_sum: 3.57 ms
f_numpy: 0.62 ms

所以使用.dot的numpy版本(抱歉,我无法找到它的文档)是最快的。如果您正在进行大量的数值运算(看起来是这样),那么在创建它们时,将fieldsweights转换为numpy数组可能是值得的。

update_weights

Numpy很可能为update_weights提供类似的加速,例如:

def update_weights(weights, fields, to_increase, to_decrease):
  weights[to_increase,:] += fields
  weights[to_decrease,:] -= fields
  return weights

(我没有测试或描述过顺便说一句,你需要这样做。)

答案 1 :(得分:4)

我认为你可以使用numpy获得相当大的速度提升。愚蠢的例子:

>>> fields = numpy.array([1, 4, 1, 3, 2, 5, 1])
>>> weights = numpy.array([[.2, .3, .4, .2, .1, .5, .9], [.3, .1, .1, .9, .2, .4, .5]])
>>> fields * weights
array([[ 0.2,  1.2,  0.4,  0.6,  0.2,  2.5,  0.9],
       [ 0.3,  0.4,  0.1,  2.7,  0.4,  2. ,  0.5]])
>>> result = _
>>> numpy.argmax(numpy.sum(result, axis=1))
1
>>> result[1]
array([ 0.3,  0.4,  0.1,  2.7,  0.4,  2. ,  0.5])

答案 2 :(得分:3)

如果你正在运行Python 2.x我会使用xrange()而不是range(),因为它不会生成列表所以会占用更少的内存

这假设你想保留当前的代码结构。

答案 3 :(得分:3)

首先,如果您使用的是Python 2.x,则可以使用xrange()代替range()来提高速度。在Python 3.x中没有xrange(),但内置range()xrange()基本相同。

接下来,如果我们要求速度,我们需要编写更少的代码,并且更多地依赖Python的内置功能(用C语言编写速度)。

您可以通过在sum()内使用生成器表达式来加快速度:

from itertools import izip

def find_best(weights,fields):
    winner = -1
    best = -float('inf')
    for c in xrange(num_category):
        score = sum(float(t[0]) * t[1] for t in izip(fields, weights[c]))
        if score > best:
            best = score
            winner = c
    return winner

再次应用相同的想法,让我们尝试使用max()来找到最佳结果。我认为这段代码很难看,但是如果你对它进行基准测试并且它足够快,那么它可能是值得的:

from itertools import izip

def find_best(weights, fields):
    tup = max(
        ((i, sum(float(t[0]) * t[1] for t in izip(fields, wlist))) for i, wlist in enumerate(weights)),
        key=lambda t: t[1]
    )
    return tup[0]

唉!但是,如果我没有犯任何错误,这也会做同样的事情,它应该依赖于Python中的C机制。测量它,看它是否更快。

所以,我们打电话给max()。我们给它一个生成器表达式,它将找到从生成器表达式返回的最大值。但是你想要最佳值的索引,所以生成器表达式返回一个元组:索引和权重值。所以我们需要将生成器表达式作为第一个参数传递,第二个参数必须是一个键函数,它从元组中查看权重值并忽略索引。由于生成器表达式不是max()的唯一参数,因此它需要在parens中。然后它构建一个i元组和计算出的权重,由我们上面使用的sum()计算得出。最后,一旦我们从max()返回一个元组,我们将其索引以获取索引值,并返回该值。

如果我们突破一个功能,我们可以减少这个丑陋。这增加了函数调用的开销,但是如果你测量它,我敢打赌它不会太慢。此外,现在我考虑一下,建立一个已经预先强制为fields的{​​{1}}值列表是有意义的。然后我们可以多次使用它。此外,不是使用float并行迭代两个列表,而是让它只是创建一个迭代器并明确地询问它的值。在Python 2.x中,我们使用izip()方法函数来请求值;在Python 3.x中,您将使用.next()内置函数。

next()

如果有30K字段值,那么预先计算def fweight(field_float_list, wlist): f = iter(field_float_list) return sum(f.next() * w for w in wlist) def find_best(weights, fields): flst = [float(x) for x in fields] tup = max( ((i, fweight(flst, wlist)) for i, wlist in enumerate(weights)), key=lambda t: t[1] ) return tup[0] 值可能是一个很大的速度胜利。

编辑:我错过了一招。而不是float()函数,我应该像接受的答案中的一些代码一样使用lambda。此外,接受的答案定时事项,它看起来像函数调用的开销是重要的。但是Numpy的答案要快得多,再也不值得玩这个答案了。

至于第二部分,我认为不能加速。我会试试:

operator.itemgetter()

因此,我们不是迭代def update_weights(weights,fields,toincrease,todecrease): w_inc = weights[toincrease] w_dec = weights[todecrease] for i, f in enumerated(fields): f = float(f) # see note below w_inc[i] += f w_dec[i] -= f ,而是直接迭代字段值。我们有一条强制浮动的线。

请注意,如果权重值已经浮动,我们实际上并不需要强制浮动,我们可以通过删除该行来节省时间。

您的代码将权重列表编入索引四次:两次执行增量,两次执行减量。此代码只执行一次第一个索引(使用xrange()toincrease)参数。它仍然必须按todecrease编制索引才能使i生效。 (我的第一个版本尝试使用迭代器来避免这种情况而且没有用。我应该在发布之前进行测试。但现在已经修复了。)

要尝试的最后一个版本:不使用递增和递减值,只需使用列表推导来构建包含我们想要的值的新列表:

+=

这假设您已经强制所有字段值浮动,如上所示。

以这种方式替换整个列表是更快还是更慢?我会猜得更快,但我不确定。测量并看!

哦,我应该补充一下:请注意,我上面显示的def update_weights(weights, field_float_list, toincrease, todecrease): f = iter(field_float_list) weights[toincrease] = [x + f.next() for x in weights[toincrease]] f = iter(field_float_list) weights[todecrease] = [x - f.next() for x in weights[todecrease]] 版本不会返回update_weights()。这是因为在Python中,不会从改变数据结构的函数返回值被认为是一种好习惯,只是为了确保没有人对查询哪些函数以及哪些函数改变事物感到困惑。

http://en.wikipedia.org/wiki/Command-query_separation

衡量措施措施。看看我的建议有多快,或者不是。

答案 4 :(得分:2)

轻松优化是使用xrange代替rangexrange是一个生成器函数,yields在迭代时逐个生成;而range首先使用更多内存和CPU周期创建整个(30,000项)列表作为临时对象。

答案 5 :(得分:2)

正如@Levon所说,python2.x中的xrange()是必须的。另外,如果你在python2.4 +中,你可以使用generator expression(感谢@steveha),有点像list comprehensions(仅在2.6+),你的内部循环如下:< / p>

for i in range(num_fields):
      score += float(fields[i]) * weights[c][i]

相当于

score = sum(float(fields[i]) * weights[c][i]) for i in num_fields)

同样一般来说,python wiki上有this great page简单但有效 优化技巧!