T(i) = Tm(i) + (T(i-1)-Tm(i))**(-tau(i))
Tm
およびtau
は、以前に計算された同じ長さのNumPyベクトルであり、新しいベクトルを作成することが望まれますT
。はi
、必要なものの要素インデックスを示すためにのみ含まれています。
この場合、forループが必要ですか?
T(i) = Tm(i) + (T(i-1)-Tm(i))**(-tau(i))
Tm
およびtau
は、以前に計算された同じ長さのNumPyベクトルであり、新しいベクトルを作成することが望まれますT
。はi
、必要なものの要素インデックスを示すためにのみ含まれています。
この場合、forループが必要ですか?
あなたはこれがうまくいくと思うかもしれません:
import numpy as np
n = len(Tm)
t = np.empty(n)
t[0] = 0 # or whatever the initial condition is
t[1:] = Tm[1:] + (t[0:n-1] - Tm[1:])**(-tau[1:])
しかし、そうではありません。実際には、この方法で numpy で再帰を行うことはできません (numpy は RHS 全体を計算し、それを LHS に割り当てるため)。
したがって、この式の非再帰的なバージョンを思いつくことができない限り、明示的なループで立ち往生しています。
tt = np.empty(n)
tt[0] = 0.
for i in range(1,n):
tt[i] = Tm[i] + (tt[i-1] - Tm[i])**(-tau[i])
2019年更新。Numba のコードは、新しいバージョンの numba で壊れました。に変更dtype="float32"
するとdtype=np.float32
解決しました。
私はいくつかのベンチマークを実行しましたが、2019 年にNumbaを使用することは、人々が Numpy で再帰関数を高速化しようとする最初のオプションです (Aronstef の調整された提案)。Numba は既に Anaconda パッケージにプリインストールされており、最速の時間の 1 つです (どの Python よりも約 20 倍高速です)。2019 年、Python は追加の手順なしで @numba アノテーションをサポートします (少なくともバージョン 3.6、3.7、および 3.8)。2019 年 12 月 5 日、2018 年 10 月 20 日、2016 年 5 月 18 日に実行された 3 つのベンチマークがあります。
そして、Jaffe が述べたように、2018 年にはまだ再帰関数をベクトル化することはできません。Aronstef によるベクトル化を確認しましたが、機能しません。
実行時間でソートされたベンチマーク:
-------------------------------------------
|Variant |2019-12 |2018-10 |2016-05 |
-------------------------------------------
|Pure C | na | na | 2.75 ms|
|C extension | na | na | 6.22 ms|
|Cython float32 | 0.55 ms| 1.01 ms| na |
|Cython float64 | 0.54 ms| 1.05 ms| 6.26 ms|
|Fortran f2py | 4.65 ms| na | 6.78 ms|
|Numba float32 |73.0 ms| 2.81 ms| na |
|(Aronstef) | | | |
|Numba float32v2| 1.82 ms| 2.81 ms| na |
|Numba float64 |78.9 ms| 5.28 ms| na |
|Numba float64v2| 4.49 ms| 5.28 ms| na |
|Append to list |73.3 ms|48.2 ms|91.0 ms|
|Using a.item() |36.9 ms|58.3 ms|74.4 ms|
|np.fromiter() |60.8 ms|60.0 ms|78.1 ms|
|Loop over Numpy|71.3 ms|71.9 ms|87.9 ms|
|(Jaffe) | | | |
|Loop over Numpy|74.6 ms|74.4 ms| na |
|(Aronstef) | | | |
-------------------------------------------
対応するコードは、回答の最後に記載されています。
時間が経つにつれて、Numba と Cython の時間は良くなっているようです。どちらも Fortran f2py よりも高速になりました。Cython は現在 8.6 倍高速で、Numba 32bit は 2.5 倍高速です。2016 年に Fortran をデバッグしてコンパイルするのは非常に困難でした。そのため、今では Fortran を使用する理由はまったくありません。
Jupyter Notebook でコンパイルするのは簡単ではないため、2019 年と 2018 年に Pure C と C 拡張機能をチェックしませんでした。
2019年には次の設定がありました。
Processor: Intel i5-9600K 3.70GHz
Versions:
Python: 3.8.0
Numba: 0.46.0
Cython: 0.29.14
Numpy: 1.17.4
私は2018年に次の設定をしました:
Processor: Intel i7-7500U 2.7GHz
Versions:
Python: 3.7.0
Numba: 0.39.0
Cython: 0.28.5
Numpy: 1.15.1
float32 を使用した推奨Numbaコード (Aronstef を調整):
@numba.jit("float32[:](float32[:], float32[:])", nopython=True, nogil=True)
def calc_py_jit32v2(Tm_, tau_):
tt = np.empty(len(Tm_),dtype=np.float32)
tt[0] = Tm_[0]
for i in range(1, len(Tm_)):
tt[i] = Tm_[i] - (tt[i-1] + Tm_[i])**(-tau_[i])
return tt[1:]
他のすべてのコード:
データの作成 (Aronstef + Mike T のコメントなど):
np.random.seed(0)
n = 100000
Tm = np.cumsum(np.random.uniform(0.1, 1, size=n).astype('float64'))
tau = np.random.uniform(-1, 0, size=n).astype('float64')
ar = np.column_stack([Tm,tau])
Tm32 = Tm.astype('float32')
tau32 = tau.astype('float32')
Tm_l = list(Tm)
tau_l = list(tau)
2016 年のコードは、マイク T のバリアントではなく、nans を防ぐために abs() 関数を使用したため、わずかに異なっていました。2018 年の関数は、OP (元のポスター) が書いたものとまったく同じです。
Jupyter %% マジックを使用したCython float32 。関数は で直接使用できますPython
。Cython には、Python がコンパイルされた C++ コンパイラが必要です。適切なバージョンの Visual C++ コンパイラ (Windows 用) をインストールすると、問題が発生する可能性があります。
%%cython
import cython
import numpy as np
cimport numpy as np
from numpy cimport ndarray
cdef extern from "math.h":
np.float32_t exp(np.float32_t m)
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.infer_types(True)
@cython.initializedcheck(False)
def cy_loop32(np.float32_t[:] Tm,np.float32_t[:] tau,int alen):
cdef np.float32_t[:] T=np.empty(alen, dtype=np.float32)
cdef int i
T[0]=0.0
for i in range(1,alen):
T[i] = Tm[i] + (T[i-1] - Tm[i])**(-tau[i])
return T
Jupyter %% マジックを使用したCython float64 。この関数は、次の場所で直接使用できますPython
。
%%cython
cdef extern from "math.h":
double exp(double m)
import cython
import numpy as np
cimport numpy as np
from numpy cimport ndarray
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.infer_types(True)
@cython.initializedcheck(False)
def cy_loop(double[:] Tm,double[:] tau,int alen):
cdef double[:] T=np.empty(alen)
cdef int i
T[0]=0.0
for i in range(1,alen):
T[i] = Tm[i] + (T[i-1] - Tm[i])**(-tau[i])
return T
ナンバー float64:
@numba.jit("float64[:](float64[:], float64[:])", nopython=False, nogil=True)
def calc_py_jitv2(Tm_, tau_):
tt = np.empty(len(Tm_),dtype=np.float64)
tt[0] = Tm_[0]
for i in range(1, len(Tm_)):
tt[i] = Tm_[i] - (tt[i-1] + Tm_[i])**(-tau_[i])
return tt[1:]
リストに追加します。コンパイルされていない最速のソリューション:
def rec_py_loop(Tm,tau,alen):
T = [Tm[0]]
for i in range(1,alen):
T.append(Tm[i] - (T[i-1] + Tm[i])**(-tau[i]))
return np.array(T)
a.item() の使用:
def rec_numpy_loop_item(Tm_,tau_):
n_ = len(Tm_)
tt=np.empty(n_)
Ti=tt.item
Tis=tt.itemset
Tmi=Tm_.item
taui=tau_.item
Tis(0,Tm_[0])
for i in range(1,n_):
Tis(i,Tmi(i) - (Ti(i-1) + Tmi(i))**(-taui(i)))
return tt[1:]
np.fromiter():
def it(Tm,tau):
T=Tm[0]
i=0
while True:
yield T
i+=1
T=Tm[i] - (T + Tm[i])**(-tau[i])
def rec_numpy_iter(Tm,tau,alen):
return np.fromiter(it(Tm,tau), np.float64, alen)[1:]
Numpy をループします (Jaffe のアイデアに基づく):
def rec_numpy_loop(Tm,tau,alen):
tt=np.empty(alen)
tt[0]=Tm[0]
for i in range(1,alen):
tt[i] = Tm[i] - (tt[i-1] + Tm[i])**(-tau[i])
return tt[1:]
Numpy (Aronstef のコード) をループします。私のコンピュータfloat64
では、 が のデフォルトのタイプですnp.empty
。
def calc_py(Tm_, tau_):
tt = np.empty(len(Tm_),dtype="float64")
tt[0] = Tm_[0]
for i in range(1, len(Tm_)):
tt[i] = (Tm_[i] - (tt[i-1] + Tm_[i])**(-tau_[i]))
return tt[1:]
Python
まったく使わずにピュアC。2016 年以降のバージョン (fabs() 関数を使用):
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <windows.h>
#include <sys\timeb.h>
double randn() {
double u = rand();
if (u > 0.5) {
return sqrt(-1.57079632679*log(1.0 - pow(2.0 * u - 1, 2)));
}
else {
return -sqrt(-1.57079632679*log(1.0 - pow(1 - 2.0 * u,2)));
}
}
void rec_pure_c(double *Tm, double *tau, int alen, double *T)
{
for (int i = 1; i < alen; i++)
{
T[i] = Tm[i] + pow(fabs(T[i - 1] - Tm[i]), (-tau[i]));
}
}
int main() {
int N = 100000;
double *Tm= calloc(N, sizeof *Tm);
double *tau = calloc(N, sizeof *tau);
double *T = calloc(N, sizeof *T);
double time = 0;
double sumtime = 0;
for (int i = 0; i < N; i++)
{
Tm[i] = randn();
tau[i] = randn();
}
LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
LARGE_INTEGER Frequency;
for (int j = 0; j < 1000; j++)
{
for (int i = 0; i < 3; i++)
{
QueryPerformanceFrequency(&Frequency);
QueryPerformanceCounter(&StartingTime);
rec_pure_c(Tm, tau, N, T);
QueryPerformanceCounter(&EndingTime);
ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
ElapsedMicroseconds.QuadPart *= 1000000;
ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
if (i == 0)
time = (double)ElapsedMicroseconds.QuadPart / 1000;
else {
if (time > (double)ElapsedMicroseconds.QuadPart / 1000)
time = (double)ElapsedMicroseconds.QuadPart / 1000;
}
}
sumtime += time;
}
printf("1000 loops,best of 3: %.3f ms per loop\n",sumtime/1000);
free(Tm);
free(tau);
free(T);
}
Fortran f2py. から機能を利用できますPython
。2016 年のバージョン (abs() 関数を使用):
subroutine rec_fortran(tm,tau,alen,result)
integer*8, intent(in) :: alen
real*8, dimension(alen), intent(in) :: tm
real*8, dimension(alen), intent(in) :: tau
real*8, dimension(alen) :: res
real*8, dimension(alen), intent(out) :: result
res(1)=0
do i=2,alen
res(i) = tm(i) + (abs(res(i-1) - tm(i)))**(-tau(i))
end do
result=res
end subroutine rec_fortran