2016-07-22 15 views
0

私はnumbaでスピードアップしようとしているいくつかのコードがあります。私はその話題についていくつか読んだことがありますが、私は100%それを理解することができませんでした。ここでNumbaは機能を高速化しません

コードされています

import pandas as pd 
import matplotlib.pyplot as plt 
import numpy as np 
import scipy.stats as st 
import seaborn as sns 
from numba import jit, vectorize, float64, autojit 
sns.set(context='talk', style='ticks', font_scale=1.2, rc={'figure.figsize': (6.5, 5.5), 'xtick.direction': 'in', 'ytick.direction': 'in'}) 

#%% constraints 
x_min = 0        # death below this 
x_max = 20        # maximum weight 
t_max = 100        # maximum time 
foraging_efficiencies = np.linspace(0, 1, 10)    # potential foraging efficiencies 
R = 10.0         # Resource level 

#%% make the body size and time categories 
body_sizes = np.arange(x_min, x_max+1) 
time_steps = np.arange(t_max) 

#%% parameter functions 
@jit 
def metabolic_fmr(x, u,temp):       # metabolic cost function 
    fmr = 0.125*(2**(0.2*temp))*(1 + 0.5*u) + x*0.1 
    return fmr 

def intake_dist(u):       # intake stochastic function (returns a vector) 
    g = st.binom.pmf(np.arange(R+1), R, u) 
    return g 

@jit 
def mass_gain(x, u, temp):      # mass gain function (returns a vector) 
    x_prime = x - metabolic_fmr(x, u,temp) + np.arange(R+1) 
    x_prime = np.minimum(x_prime, x_max) 
    x_prime = np.maximum(x_prime, 0) 
    return x_prime 

@jit 
def prob_attack(P):       # probability of an attack 
    p_a = 0.02*P 
    return p_a 

@jit 
def prob_see(u):       # probability of not seeing an attack 
    p_s = 1-(1-u)**0.3 
    return p_s 

@jit 
def prob_lethal(x):       # probability of lethality given a successful attack 
    p_l = 0.5*np.exp(-0.05*x) 
    return p_l 

@jit 
def prob_mort(P, u, x): 
    p_m = prob_attack(P)*prob_see(u)*prob_lethal(x) 
    return np.minimum(p_m, 1) 

#%% terminal fitness function 
@jit 
def terminal_fitness(x): 
    t_f = 15.0*x/(x+5.0) 
    return t_f 

#%% linear interpolation function 
@jit 
def linear_interpolation(x, F, t): 
    floor = x.astype(int) 
    delta_c = x-floor 
    ceiling = floor + 1 
    ceiling[ceiling>x_max] = x_max 
    floor[floor<x_min] = x_min 
    interpolated_F = (1-delta_c)*F[floor,t] + (delta_c)*F[ceiling,t] 
    return interpolated_F 

#%% solver 
@jit 
def solver_jit(P, temp): 
    F = np.zeros((len(body_sizes), len(time_steps)))   # Expected fitness 
    F[:,-1] = terminal_fitness(body_sizes)    # expected terminal fitness for every body size 
    V = np.zeros((len(foraging_efficiencies), len(body_sizes), len(time_steps)))  # Fitness for each foraging effort 
    D = np.zeros((len(body_sizes), len(time_steps)))   # Decision 
    for t in range(t_max-1)[::-1]: 
     for x in range(x_min+1, x_max+1):    # iterate over every body size except dead 
      for i in range(len(foraging_efficiencies)):  # iterate over every possible foraging efficiency 
       u = foraging_efficiencies[i] 
       g_u = intake_dist(u)    # calculate the distribution of intakes 
       xp = mass_gain(x, u, temp)   # calculate the mass gain 
       p_m = prob_mort(P, u, x)   # probability of mortality 
       V[i,x,t] = (1 - p_m)*(linear_interpolation(xp, F, t+1)*g_u).sum()  # Fitness calculation 
      vmax = V[:,x,t].max() 
      idx = np.argwhere(V[:,x,t]==vmax).min() 
      D[x,t] = foraging_efficiencies[idx] 
      F[x,t] = vmax 
    return D, F 

def solver_norm(P, temp): 
    F = np.zeros((len(body_sizes), len(time_steps)))   # Expected fitness 
    F[:,-1] = terminal_fitness(body_sizes)    # expected terminal fitness for every body size 
    V = np.zeros((len(foraging_efficiencies), len(body_sizes), len(time_steps)))  # Fitness for each foraging effort 
    D = np.zeros((len(body_sizes), len(time_steps)))   # Decision 
    for t in range(t_max-1)[::-1]: 
     for x in range(x_min+1, x_max+1):    # iterate over every body size except dead 
      for i in range(len(foraging_efficiencies)):  # iterate over every possible foraging efficiency 
       u = foraging_efficiencies[i] 
       g_u = intake_dist(u)    # calculate the distribution of intakes 
       xp = mass_gain(x, u, temp)   # calculate the mass gain 
       p_m = prob_mort(P, u, x)   # probability of mortality 
       V[i,x,t] = (1 - p_m)*(linear_interpolation(xp, F, t+1)*g_u).sum()  # Fitness calculation 
      vmax = V[:,x,t].max() 
      idx = np.argwhere(V[:,x,t]==vmax).min() 
      D[x,t] = foraging_efficiencies[idx] 
      F[x,t] = vmax 
    return D, F 

個々のJIT機能がun-JITコンパイルされたものよりもはるかに高速になる傾向があります。たとえば、prob_mortはjitで実行されると約600%高速です。しかし、ソルバー自身ははるかに高速ではありません。

In [3]: %timeit -n 10 solver_jit(200, 25) 
10 loops, best of 3: 3.94 s per loop 

In [4]: %timeit -n 10 solver_norm(200, 25) 
10 loops, best of 3: 4.09 s per loop 

私はいくつかの機能がJITコンパイルすることはできません知っているので、私はカスタムJIT機能付きst.binom.pmf機能を置き換え、それが実際に時間をスローダウン1ループあたり約17秒、5倍以上遅くなります。おそらく、scipy関数は、この時点で、大きく最適化されているからです。

私は遅れがlinear_interpolate関数か、ジッタ関数の外側のソルバーコードのどこかにあると考えています(ある時点で、すべての関数をun-jittedしてsolver_normを実行して、同じ時間を得たからです)。遅い部分がどこにあるのか、それをどのようにスピードアップするかについての考えはありますか?

UPDATE

は、ここで私は私がnopythonモードで私の二項のコードを実行しようとした私だったが分かったJIT

@jit 
def factorial(n): 
    if n==0: 
     return 1 
    else: 
     return n*factorial(n-1) 

@vectorize([float64(float64,float64,float64)]) 
def binom(k, n, p): 
    binom_coef = factorial(n)/(factorial(k)*factorial(n-k)) 
    pmf = binom_coef*p**k*(1-p)**(n-k) 
    return pmf 

@jit 
def intake_dist(u):       # intake stochastic function (returns a vector) 
    g = binom(np.arange(R+1), R, u) 
    return g 

UPDATE 2 をスピードアップするための試みで使用される二項のコードですそれは再帰的なので間違っています。ことを定着時にコードを変更することにより:

@jit(int64(int64), nopython=True) 
def factorial(nn): 
    res = 1 
    for ii in range(2, nn + 1): 
     res *= ii 
    return res 

@vectorize([float64(float64,float64,float64)], nopython=True) 
def binom(k, n, p): 
    binom_coef = factorial(n)/(factorial(k)*factorial(n-k)) 
    pmf = binom_coef*p**k*(1-p)**(n-k) 
    return pmf 

ソルバーは現在約3.5倍高速である

In [34]: %timeit solver_jit(200, 25) 
1 loop, best of 3: 921 ms per loop 

で実行します。しかし、solver_jit()とsolver_norm()は同じペースで実行されています。つまり、jit関数の外にあるコードが減速しているためです。

+0

カスタム 'binom.pmf'機能を投稿することができますか?私は、あなたがジッタを使用して改善を得られない理由は、あなたの最も内側のループに 'intake_dist'があり、これはジッとすることができないとあなたのソルバーで"オブジェクトモード "を使用しているということです。 – JoshAdel

+0

'binom.pmf'がボトルネックになっている場合は、rmathのバージョンをラッピングし、cffiを使って呼び出すことができます(このブログ記事ではhttps://www.continuum.io/blog/developer-blog/calling- c-libraries-numba-using-cffi – JoshAdel

答えて

1

コードを少し変更して、nopythonモードで完全にコンパイルできるようにしました。私のラップトップでは、次のようになります。

%timeit solver_jit(200, 25) 
1 loop, best of 3: 50.9 ms per loop 

%timeit solver_norm(200, 25) 
1 loop, best of 3: 192 ms per loop 

参考のため、Numba 0.27.0を使用しています。私はNumbaのコンパイルエラーによって、何が起こっているのかを特定することが困難になっていることは認められますが、私はしばらくそれをしてきたので、修正する必要のある直感を築きました。完全なコードは以下の通りですが、ここで私が行った変更の一覧です:

  • x.astype(np.int64)からlinear_interpolation変更x.astype(int)では、それはnopythonモードでコンパイルできるよう。
  • ソルバでは、配列のメソッドではなく、関数としてnp.sumを使用します。
  • np.argwhereはサポートされていません。カスタムループを作成します。

これ以上の最適化が行われる可能性がありますが、これにより初期スピードアップが可能になります。

フルコード:

import pandas as pd 
import matplotlib.pyplot as plt 
import numpy as np 
import scipy.stats as st 
import seaborn as sns 
from numba import jit, vectorize, float64, autojit, njit 
sns.set(context='talk', style='ticks', font_scale=1.2, rc={'figure.figsize': (6.5, 5.5), 'xtick.direction': 'in', 'ytick.direction': 'in'}) 

#%% constraints 
x_min = 0        # death below this 
x_max = 20        # maximum weight 
t_max = 100        # maximum time 
foraging_efficiencies = np.linspace(0, 1, 10)    # potential foraging efficiencies 
R = 10.0         # Resource level 

#%% make the body size and time categories 
body_sizes = np.arange(x_min, x_max+1) 
time_steps = np.arange(t_max) 

#%% parameter functions 
@njit 
def metabolic_fmr(x, u,temp):       # metabolic cost function 
    fmr = 0.125*(2**(0.2*temp))*(1 + 0.5*u) + x*0.1 
    return fmr 

@njit() 
def factorial(nn): 
    res = 1 
    for ii in range(2, nn + 1): 
     res *= ii 
    return res 

@vectorize([float64(float64,float64,float64)], nopython=True) 
def binom(k, n, p): 
    binom_coef = factorial(n)/(factorial(k)*factorial(n-k)) 
    pmf = binom_coef*p**k*(1-p)**(n-k) 
    return pmf 

@njit 
def intake_dist(u):       # intake stochastic function (returns a vector) 
    g = binom(np.arange(R+1), R, u) 
    return g 

@njit 
def mass_gain(x, u, temp):      # mass gain function (returns a vector) 
    x_prime = x - metabolic_fmr(x, u,temp) + np.arange(R+1) 
    x_prime = np.minimum(x_prime, x_max) 
    x_prime = np.maximum(x_prime, 0) 
    return x_prime 

@njit 
def prob_attack(P):       # probability of an attack 
    p_a = 0.02*P 
    return p_a 

@njit 
def prob_see(u):       # probability of not seeing an attack 
    p_s = 1-(1-u)**0.3 
    return p_s 

@njit 
def prob_lethal(x):       # probability of lethality given a successful attack 
    p_l = 0.5*np.exp(-0.05*x) 
    return p_l 

@njit 
def prob_mort(P, u, x): 
    p_m = prob_attack(P)*prob_see(u)*prob_lethal(x) 
    return np.minimum(p_m, 1) 

#%% terminal fitness function 
@njit 
def terminal_fitness(x): 
    t_f = 15.0*x/(x+5.0) 
    return t_f 

#%% linear interpolation function 
@njit 
def linear_interpolation(x, F, t): 
    floor = x.astype(np.int64) 
    delta_c = x-floor 
    ceiling = floor + 1 
    ceiling[ceiling>x_max] = x_max 
    floor[floor<x_min] = x_min 
    interpolated_F = (1-delta_c)*F[floor,t] + (delta_c)*F[ceiling,t] 
    return interpolated_F 

#%% solver 
@njit 
def solver_jit(P, temp): 
    F = np.zeros((len(body_sizes), len(time_steps)))   # Expected fitness 
    F[:,-1] = terminal_fitness(body_sizes)    # expected terminal fitness for every body size 
    V = np.zeros((len(foraging_efficiencies), len(body_sizes), len(time_steps)))  # Fitness for each foraging effort 
    D = np.zeros((len(body_sizes), len(time_steps)))   # Decision 
    for t in range(t_max-2,-1,-1): 
     for x in range(x_min+1, x_max+1):    # iterate over every body size except dead 
      for i in range(len(foraging_efficiencies)):  # iterate over every possible foraging efficiency 
       u = foraging_efficiencies[i] 
       g_u = intake_dist(u)    # calculate the distribution of intakes 
       xp = mass_gain(x, u, temp)   # calculate the mass gain 
       p_m = prob_mort(P, u, x)   # probability of mortality 
       V[i,x,t] = (1 - p_m)*np.sum((linear_interpolation(xp, F, t+1)*g_u))  # Fitness calculation 
      vmax = V[:,x,t].max() 

      for k in xrange(V.shape[0]): 
       if V[k,x,t] == vmax: 
        idx = k 
        break 
      #idx = np.argwhere(V[:,x,t]==vmax).min() 
      D[x,t] = foraging_efficiencies[idx] 
      F[x,t] = vmax 
    return D, F 

def solver_norm(P, temp): 
    F = np.zeros((len(body_sizes), len(time_steps)))   # Expected fitness 
    F[:,-1] = terminal_fitness(body_sizes)    # expected terminal fitness for every body size 
    V = np.zeros((len(foraging_efficiencies), len(body_sizes), len(time_steps)))  # Fitness for each foraging effort 
    D = np.zeros((len(body_sizes), len(time_steps)))   # Decision 
    for t in range(t_max-1)[::-1]: 
     for x in range(x_min+1, x_max+1):    # iterate over every body size except dead 
      for i in range(len(foraging_efficiencies)):  # iterate over every possible foraging efficiency 
       u = foraging_efficiencies[i] 
       g_u = intake_dist(u)    # calculate the distribution of intakes 
       xp = mass_gain(x, u, temp)   # calculate the mass gain 
       p_m = prob_mort(P, u, x)   # probability of mortality 
       V[i,x,t] = (1 - p_m)*(linear_interpolation(xp, F, t+1)*g_u).sum()  # Fitness calculation 
      vmax = V[:,x,t].max() 
      idx = np.argwhere(V[:,x,t]==vmax).min() 
      D[x,t] = foraging_efficiencies[idx] 
      F[x,t] = vmax 
    return D, F 
+0

ありがとう!これは私のコードをスピードアップし、1ランごとに4.02秒から1ランごとに320ミリ秒になりました! np.int64を使った線形補間のトリックは、Pythonモードではコンパイルできないという問題が何であったのか分からなかったので、私のハングアップでした。それだけで時間が3秒短縮されました。私はargwがサポートされていないことも知っていましたが、代わりにカスタムコードを持っていませんでした。助けてくれてありがとう! 320ミリ秒は巨大な改善です! – Nate

0

このように、オブジェクトモードに戻っているコードがあります。私は、オブジェクトモードを無効にするためにjitの代わりにnjitを使用できるということを追加したかっただけです。これは、コードが原因であるかどうかを診断するのに役立ちます。

関連する問題