巧妙使用NumPy加快循环速度

时间:2020-05-28 15:49:54

标签: python arrays numpy scipy vectorization

这是一个理论上的问题。这是有关所附代码末尾的for循环的说明。

我有三个兼容的数组,Z,Wt_1和Wt_2。

我使用矢量化的列来加快循环速度,即我使用列进行编码,就好像它们是标量一样,整个过程都可以进行。 但是,此循环仍需要花费永久时间(超过2分钟)。在这里,N = 5000,列长100000。

是否有语法可以加快速度?

M=100000

N=5000
t0=0.132
T=0.22
dt=T / N

Z0=8
Y0=7

ssqZ=0.04
ssqY=0.01
sigma_Z=np.sqrt(ssqZ)
sigma_Y=np.sqrt(ssqY)
rho=- 0.4

print("Genreating the Brownian Motions...")
Wt_1=rd.randn(M//2,N)
# shape is (50 000,5 000)
Wt_1=np.vstack((Wt_1,-Wt_1))
#print(np.shape(Wt_1))
Wt_2=rd.randn(M//2,N)
Wt_2=np.vstack((Wt_2,-Wt_2))
print("BM generated!\n")

Z=np.zeros((M,N+1))
# shape is (100 000, 5 001)
Y=np.zeros((M,N+1))
#in SciPy indexing starts from 0 :
Z[:,0]=Z0
Y[:,0]=Y0

for i in range(1,N+1) :
    Z[:,i]=Z[:,i-1] - 0.5*(sigma_Z**2)*dt + sigma_Z*np.sqrt(dt)*Wt_1[:,i-1]

    Y[:,i]=Y[:,i-1] + sigma_Y*np.sqrt(dt)* (rho*Wt_1[:,i-1] + np.sqrt(1 - rho**2)*Wt_2[:,i-1])

我应该补充一点,Matlab中的同一例程花费19s,而此Python例程花费137s,这对我来说很直观

4 个答案:

答案 0 :(得分:1)

这个问题很难有效地向量化Numpy。在您的版本中,您具有矢量化的命令,该命令可对未连续存储在存储器Numpy/C row-major, Fortran/Matlab/Julia is column major.

中的值进行迭代

如果要使用Numpy,最简单的方法可能是转置所有数组和操作或使用Fortran排序的数组(应为4)。

另一种方法是写出循环并使用Numba Numba或Cython

Numba示例

@nb.njit(fastmath=True,parallel=True,cache=True)
def Numba(Z0,Y0,sigma_Y,sigma_Z,Wt_1,Wt_2,rho,dt):
    N,M=Wt_1.shape

    Z=np.empty((M,N+1))
    Y=np.empty((M,N+1))
    Z[:,0]=Z0
    Y[:,0]=Y0
    #the possibillity of negative values can lead to a slow wrap-around check
    for i in nb.prange(M):
        #only beneficial for very large 
        for j in range(N):
            Z[i,j+1]=Z[i,j] - 0.5*(sigma_Z**2)*dt + sigma_Z*np.sqrt(dt)*Wt_1[i,j]
            Y[i,j+1]=Y[i,j] + sigma_Y*np.sqrt(dt)* (rho*Wt_1[i,j] + np.sqrt(1 - rho**2)*Wt_2[i,j])
    return Z,Y

您的Numpy示例

最好添加一个示例,说明如何获得计时,因为生成输入要比实际计算花费更多的时间(至少对于Numba版本而言)。

def Numpy_not_aligned(Z0,Y0,sigma_Y,sigma_Z,Wt_1,Wt_2,rho,dt):
    Z=np.zeros((M,N+1))
    # shape is (100 000, 5 001)
    Y=np.zeros((M,N+1))
    #in SciPy indexing starts from 0 :
    Z[:,0]=Z0
    Y[:,0]=Y0

    for i in range(1,N+1) :
        Z[:,i]=Z[:,i-1] - 0.5*(sigma_Z**2)*dt + sigma_Z*np.sqrt(dt)*Wt_1[:,i-1]
        Y[:,i]=Y[:,i-1] + sigma_Y*np.sqrt(dt)* (rho*Wt_1[:,i-1] + np.sqrt(1 - rho**2)*Wt_2[:,i-1])
    return Y,Z

时间

# M=M=50000
%timeit Numpy_not_aligned(Z0,Y0,sigma_Y,sigma_Z,Wt_1,Wt_2,rho,dt)
#25.9 s ± 82.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)    
%timeit Numba(Z0,Y0,sigma_Y,sigma_Z,Wt_1,Wt_2,rho,dt)
#1.03 s ± 127 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

答案 1 :(得分:0)

我试图将计算移出循环。底部表达式似乎是简单的矩阵减法,因此我将其完全取出。

sqrt_dt = np.sqrt(dt)
A = 0.5*(sigma_Z**2)*dt
B = sigma_Z* sqrt_dt
C = sigma_Y * sqrt_dt * (rho*Wt_1 + np.sqrt(1 - rho**2)*Wt_2)

Y[:,1:N+1] = Y[:,0:N] - C[:,0:N]

for i in range(1,N+1) :
    Z[:,i]=Z[:,i-1] - A + B  * Wt_1[:,i-1]

希望这会有所帮助!

答案 2 :(得分:0)

让我们尝试将一些重复的计算移出循环

sub_1 = 0.5*(sigma_Z**2)*dt
sub_2 = sigma_Z*np.sqrt(dt)
sub_3 = np.sqrt(1 - rho**2)
sub_4 = sigma_Y*np.sqrt(dt)

for i in range(1,N+1) :
    Z[:,i]=np.add(np.subtract(Z[:,i-1] ,sub_1), np.multiply(Wt_1[:,i-1], sub_2))

    Y[:,i]=np.add(Y[:,i-1] , np.multiply(np.add(np.multiply(Wt_2[:,i-1],sub_3),np.multiply(Wt_1[:,i-1],rho)), sub_4))

答案 3 :(得分:0)

除非消除循环,否则您将不会获得任何实际的速度改进。自然地,如果我们仔细看一下,我们可以用不同的方式来公式化这两个方程。我将与第一个简短地演示Z:

Z(i) = Z(i-1) - C + D*Wt_1(i-1), where C and D are constants.
Z(1) = Z(0) - C + D*Wt_1(0)
Z(2) = Z(1) - C + D*Wt_1(1) = Z(0) - 2*C + D*(Wt_1(0)+Wt_1(1))
i.e. Z(i) = Z(0) -i*C + D*Sum(Wt_1(0..i-1))

如您所见,Z(0)项实际上是一个常数,并且存在于所有列中。我们可以在初始化期间简单地设置它。唯一的实际重复发生在Wt_1中。类似地,在Y方程中,递归取决于Wt_1和Wt_2。

我们这里有漏洞:Ws的这种重复实际上是按列递增或累加的总和,从而使我们可以舍弃for循环而改用np.cumsum。我们还可以通过将其乘以np.arange(N+1)来广播和添加Z方程中的常数C。

Z = np.ones((M,N+1))*Z0
Y = np.ones((M,N+1))*Y0
W1 = np.cumsum(Wt_1,axis=1)
W2 = np.cumsum(Wt_2,axis=1)

# Calculate Z
Z -= (0.5*(sigma_Z**2)*dt)*np.arange(N+1)
Z[:,1:] = Z[:,1:] + W1*sigma_Z*np.sqrt(dt)

# Calculate Y
Y[:,1:] = Y[:,1:] + sigma_Y*np.sqrt(dt)*(rho*W1 + np.sqrt(1 - rho**2)*W2)