4

かなり単純なコード スニペットが 2 つあり、両方を非常に多く実行しています。実行時間を短縮するためにできる最適化があるかどうかを判断しようとしています。もっと早くできるものとして際立っているものがあれば...

最初のものには、リスト、フィールドがあります。リスト、重みのリストもあります。フィールドで乗算された重みリストが最大合計を生成することを見つけようとしています。フィールドの長さは約 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

2 番目の例では、2 つの重みリストを更新しようとしています。1 つは増加し、1 つは減少します。の各要素を増減する量は、フィールドの対応する要素と同じです (たとえば、fields[4] = 10.5 の場合、weights[toincrease][4] を 10.5 増やし、weights[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

これが過度に具体的な質問でないことを願っています。

4

6 に答える 6

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

したがって、numpy バ​​ージョン.dot(申し訳ありませんが、atm のドキュメントが見つかりません) を使用するのが最速です。多くの数値演算を行っている場合 (そうであるように思われます)、作成したらすぐに numpy 配列に変換する価値があるかもしれませfieldsweights

update_weights

update_weightsNumpy は、次のようなことを行って、同様の高速化を提供する可能性があります。

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

(私はそれをテストしたりプロファイリングしたりしていません。それを行う必要があります。)

于 2012-04-13T02:19:19.920 に答える
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])
于 2012-04-13T01:28:35.403 に答える
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()います。ジェネレーター式を指定すると、ジェネレーター式から返される最大値が検出されます。しかし、最良の値のインデックスが必要なため、ジェネレータ式はインデックスと重み値のタプルを返します。したがって、ジェネレーター式を最初の引数として渡す必要があり、2 番目の引数は、タプルからの重み値を調べてインデックスを無視するキー関数でなければなりません。ジェネレーター式は唯一の引数ではないためmax()、括弧で囲む必要があります。i次に、上で使用したのと同じ方法で計算された、計算された重みのタプルを作成しsum()ます。最後に、 からタプルをmax()取得したら、インデックスを作成してインデックス値を取得し、それを返します。

関数を分解すれば、これをもっと醜くすることができます。これにより、関数呼び出しのオーバーヘッドが追加されますが、測定すると、それほど遅くはないと思います。また、考えてみると、すでに;fieldsに事前に強制されている値のリストを作成することは理にかなっています。floatその後、それを複数回使用できます。また、 を使用して 2 つのリストを並行して反復処理する代わりにizip()、反復子を作成して明示的に値を要求しましょう。Python 2.x では、.next()メソッド関数を使用して値を要求します。Python 3.x ではnext()組み込み関数を使用します。

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]

30,000 個のフィールド値がある場合、値を事前に計算float()すると、速度が大幅に向上する可能性があります。

編集:1つのトリックを逃しました。lambda関数の代わりにoperator.itemgetter()、受け入れられた回答のコードの一部のように使用する必要がありました。また、受け入れられた回答のタイミングはのものであり、関数呼び出しのオーバーヘッドが大きかったようです。しかし、Numpy の回答は非常に高速だったため、この回答で遊ぶ価値はもうありません。

2番目の部分については、あまり高速化できないと思います。私が試してみます:

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()、ここではフィールド値を直接反復処理します。フロートを強制する行があります。

重みの値が既に float の場合、ここで強制的に float にする必要はなく、その行を削除するだけで時間を節約できることに注意してください。

あなたのコードは、重みリストを 4 回インデックス付けしていました。インクリメントを行うために 2 回、デクリメントを行うために 2 回です。このコードは、( toincreaseorを使用してtodecrease) 最初のインデックスを 1 回だけ実行します。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]]

これは、上記のように、すべてのフィールド値を強制的に float に設定していることを前提としています。

この方法でリスト全体を置き換える方が速いですか、遅いですか? もっと早く推測するつもりですが、よくわかりません。測って見て!

ああ、追加する必要があります: 上記の私のバージョンは をupdate_weights()返さないことに注意してくださいweights。これは、Python では、どの関数がクエリを実行し、どの関数が何かを変更するかについて誰も混乱しないようにするためだけに、データ構造を変更する関数から値を返さないことが良い習慣であると考えられているためです。

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

メジャーメジャーメジャー。私の提案がどれほど速いか、またはそうでないかを見てください。

于 2012-04-13T01:44:20.750 に答える
3

Python 2.x を実行している場合は、range() ではなく xrange() を使用します。リストを生成しないため、メモリの使用量が少なくなります。

これは、現在のコード構造を維持したいという前提です。

于 2012-04-13T01:21:31.570 に答える
2

@Levonが言うようにxrange()、python2.xでは必須です。また、python2.4+ を使用している場合は、generator expression(thanks @steveha)を使用できます。これは、リスト内包表記のように機能し (2.6+ のみ)、次のように簡単に内部ループに使用できます。

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 には、シンプルだが効果的な最適化のトリックについての素晴らしいページがあります。

于 2012-04-13T01:51:06.827 に答える
2

簡単な最適化は、xrange代わりに を使用することですrange。反復処理すると、1 つずつ結果が得xrangeられるジェネレーター関数です。yields一方、range最初にリスト全体 (30,000 項目) を一時オブジェクトとして作成し、より多くのメモリと CPU サイクルを使用します。

于 2012-04-13T01:21:48.690 に答える