背景
sigmoid関数は,2値分類やVAEの,出力層の活性化関数として使われる.
\[\mathrm{sigmoid}(x) = \frac{1}{1 + \exp(-x)}\]
定義に従って実装してもいいのだが,そうするとけっこう遅いらしいので,高速化するテクニックがある.例えばword2vecだと前に記事をかいたこんなの.
どのくらい変わるか比較してみた.
本題
python 3.6.4 (anaconda3-latest) で計測.
まず,愚直に計算する場合の関数定義.
次にword2vecやfastTextの実装にある方法.
-8
から8
までを512
分割してキャッシュしておく.
-10
から10
までを生成する一様乱数で10000
個のデータを生成する.
%timeit
で計測したいので,関数化.
rnd = np.random.RandomState(7)
data = rnd.uniform(low=-10, high=10, size=10000)
def cal_sigmoid(data):
for x in data:
sigmoid(x)
def cal_sigmoid_cache(data):
for x in data:
sigmoid_cache(x)
まず,愚直に計算する方を計測.
次に,cacheしておく方を計測.
%timeit cal_sigmoid_cache(data)
16.4 ms ± 441 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
後者がやっぱり速い.
cacheの生成は,実際にsigmoid呼ぶ回数の総数と比べれば,とても小さいため,今回は無視している.
問題としては,numpy.array
に対しては使えないかもしれないこと.
np.vectorize
関数に渡すと numpy.array
に対しても使えるようになる (助言いただいたツイート).
vectorzed_sigmoid_cache = np.vectorize(sigmoid_cache)
負けた.
ちなみに,キャッシュしないほうをmath.exp
にしても負けた.
Numba
これだとつまらなかったので numba
を使って高速化する.環境が少し新しくなって,python 3.7.2 (anaconda3-latest) の numba 0.42.0
.
といっても単純に numba
の @njit
をつけるだけ.
両方ともかなり高速化できたが,特にキャシュするほうが ns になっている.この場合だと np.ndarray
を直接渡してもキャッシュしたほうが速い.
雑に @vectorize
とかもつけたりしたがキャシュするほうは上の方法が一番速かった.