適度にパフォーマンスが重要なコードを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これらが純粋関数であることを認識し、必要に応じてキャッシュしますか?
私は何も心配していませんか?
これらの関数はすでに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.logistic
のcdf
メソッドとして使用できますが、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**2
(1/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
私のコメントを拡張して、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はこれを非常に高速に実行しますが、配列全体に対して。
このプロセスをメモ化する場合は、そのコードを関数でラップし、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
ほとんど私は 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