web-dev-qa-db-ja.com

パフォーマンス、キャッシングのためのNumpy純粋関数

適度にパフォーマンスが重要なコードをnumpyで書いています。このコードは、実行時間が時間単位で測定される計算の最も内側のループにあります。簡単な計算では、計算のバリエーションによっては、このコードが10 ^ 12回実行されることが示唆されています。

したがって、関数はsigmoid(X)を計算し、別の関数はその導関数(勾配)を計算することです。シグモイドには、
y = sigmoid(x)、dy/dx = y(1-y)
In python numpyの場合、これは次のようになります。

sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x)))
grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x)))

ご覧のとおり、両方の関数は純粋(副作用なし)であるため、メモ化の理想的な候補です。少なくとも短期的には、これまでに行われたsigmoidへのすべての呼び出しをキャッシュすることについていくつかの心配があります。数テラバイトのRAMが必要になります。

これを最適化する良い方法はありますか?
pythonこれらが純粋関数であることを認識し、必要に応じてキャッシュしますか?
私は何も心配していませんか?

11
Lyndon White

これらの関数はすでにscipyに存在します。シグモイド関数は scipy.special.expit として使用できます。

In [36]: from scipy.special import expit

expitをベクトル化されたシグモイド関数と比較します。

In [38]: x = np.linspace(-6, 6, 1001)

In [39]: %timeit y = sigmoid(x)
100 loops, best of 3: 2.4 ms per loop

In [40]: %timeit y = expit(x)
10000 loops, best of 3: 20.6 µs per loop

expitは、式を自分で実装するよりも高速です。

In [41]: %timeit y = 1.0 / (1.0 + np.exp(-x))
10000 loops, best of 3: 27 µs per loop

ロジスティック分布のCDFは、シグモイド関数です。 scipy.stats.logisticcdfメソッドとして使用できますが、cdfは最終的にexpitを呼び出すため、このメソッドを使用しても意味がありません。 pdfメソッドを使用して、シグモイド関数の導関数を計算するか、オーバーヘッドは少ないが「独自のローリング」の方が高速な_pdfメソッドを使用できます。

In [44]: def sigmoid_grad(x):
   ....:     ex = np.exp(-x)
   ....:     y = ex / (1 + ex)**2
   ....:     return y

タイミング(xの長さは1001):

In [45]: from scipy.stats import logistic

In [46]: %timeit y = logistic._pdf(x)
10000 loops, best of 3: 73.8 µs per loop

In [47]: %timeit y = sigmoid_grad(x)
10000 loops, best of 3: 29.7 µs per loop

テールの奥深くにある値を使用する場合は、実装に注意してください。指数関数は非常に簡単にオーバーフローする可能性があります。 logistic._cdfは、sigmoid_gradの簡単な実装よりも少し堅牢です。

In [60]: sigmoid_grad(-500)
/home/warren/anaconda/bin/ipython:3: RuntimeWarning: overflow encountered in double_scalars
  import sys
Out[60]: 0.0

In [61]: logistic._pdf(-500)
Out[61]: 7.1245764067412855e-218

sech**21/cosh**2)を使用した実装は、上記のsigmoid_gradよりも少し遅くなります。

In [101]: def sigmoid_grad_sech2(x):
   .....:     y = (0.5 / np.cosh(0.5*x))**2
   .....:     return y
   .....: 

In [102]: %timeit y = sigmoid_grad_sech2(x)
10000 loops, best of 3: 34 µs per loop

しかし、それは尾をよりよく処理します:

In [103]: sigmoid_grad_sech2(-500)
Out[103]: 7.1245764067412855e-218

In [104]: sigmoid_grad_sech2(500)
Out[104]: 7.1245764067412855e-218
30

私のコメントを拡張して、vectorizeを介したシグモイドとnumpyを直接使用した場合の比較を次に示します。

In [1]: x = np.random.normal(size=10000)

In [2]: sigmoid = np.vectorize(lambda x: 1.0 / (1.0 + np.exp(-x)))

In [3]: %timeit sigmoid(x)
10 loops, best of 3: 63.3 ms per loop

In [4]: %timeit 1.0 / (1.0 + np.exp(-x))
1000 loops, best of 3: 250 us per loop

ご覧のとおり、vectorizeを使用すると速度が大幅に低下するだけでなく、250マイクロ秒(つまり、それぞれ25ナノ秒)で10000シグモイドを計算できます。 Pythonでの単一の辞書検索は、メモ化を行うための他のすべてのコードは言うまでもなく、それよりも遅くなります。

私が考えることができるこれを最適化する唯一の方法は、numpyのsigmoid func を書くことです。これは、基本的にCでの操作を実装します。そうすれば、sigmoidで各操作を行う必要はありません。 numpyはこれを非常に高速に実行しますが、配列全体に対して。

5
Gustav Larsson

このプロセスをメモ化する場合は、そのコードを関数でラップし、functools.lru_cache(maxsize=n)で装飾します。 maxsize値を試して、アプリケーションに適したサイズを見つけてください。最良の結果を得るには、2の累乗であるmaxsize引数を使用します。

from functools import lru_cache

lru_cache(maxsize=8096)
def sigmoids(x):
    sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x)))
    grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x)))
    return sigmoid, grad_sigmoid

2.7を使用している場合(numpyを使用しているため、これを使用していると思います)、 https://pypi.python.org/pypi/repoze.lru/ を参照してください。構文が同じメモ化ライブラリ。

Pip経由でインストールできます:pip install repoze.lru

from repoze.lru import lru_cache

lru_cache(maxsize=8096)
def sigmoids(x):
    sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x)))
    grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x)))
    return sigmoid, grad_sigmoid
1
Madison May

ほとんど私は Warren Weckesser と彼の答え 上記 に同意します。ただし、シグモイドの導関数には、次のものを使用できます。

In [002]: def sg(x):
     ...: s = scipy.special.expit(x)
     ...: return s * (1.0 - s) 

タイミング:

In [003]: %timeit y = logistic._pdf(x)
10000 loops, best of 3: 45 µs per loop

In [004]: %timeit y = sg(x)
10000 loops, best of 3: 20.4 µs per loop

唯一の問題は精度です。

In [005]: sg(37)
Out[005]: 0.0

In [006]: logistic._pdf(37)
Out[006]: 8.5330476257440658e-17    
0
Sklavit