如何加速分析 NumPy 代码 - 矢量化,Numba?
How do I speed up profiled NumPy code - vectorizing, Numba?
我正在 运行ning 一个大型 Python 程序来优化投资组合权重,以实现金融领域的 (Markowitz) 投资组合优化。当我分析代码时,运行 的 90% 时间都花在了计算投资组合 return 上,这已经完成了数百万次。我可以做些什么来加速我的代码?我试过:
- 矢量化 returns 的计算:使代码 变慢 ,从 1.5 毫秒减少到 3 毫秒
- 使用 Numba 的 autojit 函数来加速代码:没有变化
请参阅下面的示例 - 有什么建议吗?
import numpy as np
def get_pf_returns(weights, asset_returns, horizon=60):
'''
Get portfolio returns: Calculates portfolio return for N simulations,
assuming monthly rebalancing.
Input
-----
weights: Portfolio weight for each asset
asset_returns: Monthly returns for each asset, potentially many simulations
horizon: 60 months (hard-coded)
Returns
-------
Avg. annual portfolio return for each simulation at the end of 5 years
'''
pf = np.ones(asset_returns.shape[1])
for t in np.arange(horizon):
pf *= (1 + asset_returns[t, :, :].dot(weights))
return pf ** (12.0 / horizon) - 1
def get_pf_returns2(weights, asset_returns):
''' Alternative '''
return np.prod(1 + asset_returns.dot(weights), axis=0) ** (12.0 / 60) - 1
# Example
N, T, sims = 12, 60, 1000 # Settings
weights = np.random.rand(N)
weights *= 1 / np.sum(weights) # Sample weights
asset_returns = np.random.randn(T, sims, N) / 100 # Sample returns
# Calculate portfolio risk/return
pf_returns = get_pf_returns(weights, asset_returns)
print np.mean(pf_returns), np.std(pf_returns)
# Timer
%timeit get_pf_returns(weights, asset_returns)
%timeit get_pf_returns2(weights, asset_returns)
编辑
解决方案:Matmul 在我的机器上最快:
def get_pf_returns(weights, asset_returns):
return np.prod(1 + np.matmul(asset_returns, weights), axis=0) ** (12.0 / 60) - 1
这是一个使用 np.einsum
来获得一点加速的版本:
def get_pf_returns3(weights, asset_returns, horizon=60):
pf = np.ones(asset_returns.shape[1])
z = np.einsum("ijk,k -> ij",asset_returns[:horizon,:,:], weights)
pf = np.multiply.reduce(1 + z)
return pf ** (12.0 / horizon) - 1
然后时间:
%timeit get_pf_returns(weights, asset_returns)
%timeit get_pf_returns3(weights, asset_returns)
print np.allclose(get_pf_returns(weights, asset_returns), get_pf_returns3(weights, asset_returns))
# 1000 loops, best of 3: 727 µs per loop
# 1000 loops, best of 3: 638 µs per loop
# True
您机器上的时间可能会有所不同,具体取决于硬件和 numpy 编译所针对的库。
在我的环境中,mutmul
(@
) 比 einsum
和 dot
:
有适度的时间优势
In [27]: np.allclose(np.einsum('ijk,k',asset_returns,weights),asset_returns@weig
...: hts)
Out[27]: True
In [28]: %timeit asset_returns@weights
100 loops, best of 3: 3.91 ms per loop
In [29]: %timeit np.einsum('ijk,k',asset_returns,weights)
100 loops, best of 3: 4.73 ms per loop
In [30]: %timeit np.dot(asset_returns,weights)
100 loops, best of 3: 6.8 ms per loop
我认为时间受计算总数的限制,而不是编码细节。所有这些都将计算传递给已编译的 numpy 代码。您的原始循环版本相对较快的事实可能与循环次数少(仅 60 个)以及更完整的内存管理问题有关 dot
.
并且 numba
可能不会替换 dot
代码。
因此,在这里或那里进行调整可能会使您的代码速度提高 2 倍,但不要指望会有数量级的改进。
我正在 运行ning 一个大型 Python 程序来优化投资组合权重,以实现金融领域的 (Markowitz) 投资组合优化。当我分析代码时,运行 的 90% 时间都花在了计算投资组合 return 上,这已经完成了数百万次。我可以做些什么来加速我的代码?我试过:
- 矢量化 returns 的计算:使代码 变慢 ,从 1.5 毫秒减少到 3 毫秒
- 使用 Numba 的 autojit 函数来加速代码:没有变化
请参阅下面的示例 - 有什么建议吗?
import numpy as np
def get_pf_returns(weights, asset_returns, horizon=60):
'''
Get portfolio returns: Calculates portfolio return for N simulations,
assuming monthly rebalancing.
Input
-----
weights: Portfolio weight for each asset
asset_returns: Monthly returns for each asset, potentially many simulations
horizon: 60 months (hard-coded)
Returns
-------
Avg. annual portfolio return for each simulation at the end of 5 years
'''
pf = np.ones(asset_returns.shape[1])
for t in np.arange(horizon):
pf *= (1 + asset_returns[t, :, :].dot(weights))
return pf ** (12.0 / horizon) - 1
def get_pf_returns2(weights, asset_returns):
''' Alternative '''
return np.prod(1 + asset_returns.dot(weights), axis=0) ** (12.0 / 60) - 1
# Example
N, T, sims = 12, 60, 1000 # Settings
weights = np.random.rand(N)
weights *= 1 / np.sum(weights) # Sample weights
asset_returns = np.random.randn(T, sims, N) / 100 # Sample returns
# Calculate portfolio risk/return
pf_returns = get_pf_returns(weights, asset_returns)
print np.mean(pf_returns), np.std(pf_returns)
# Timer
%timeit get_pf_returns(weights, asset_returns)
%timeit get_pf_returns2(weights, asset_returns)
编辑
解决方案:Matmul 在我的机器上最快:
def get_pf_returns(weights, asset_returns):
return np.prod(1 + np.matmul(asset_returns, weights), axis=0) ** (12.0 / 60) - 1
这是一个使用 np.einsum
来获得一点加速的版本:
def get_pf_returns3(weights, asset_returns, horizon=60):
pf = np.ones(asset_returns.shape[1])
z = np.einsum("ijk,k -> ij",asset_returns[:horizon,:,:], weights)
pf = np.multiply.reduce(1 + z)
return pf ** (12.0 / horizon) - 1
然后时间:
%timeit get_pf_returns(weights, asset_returns)
%timeit get_pf_returns3(weights, asset_returns)
print np.allclose(get_pf_returns(weights, asset_returns), get_pf_returns3(weights, asset_returns))
# 1000 loops, best of 3: 727 µs per loop
# 1000 loops, best of 3: 638 µs per loop
# True
您机器上的时间可能会有所不同,具体取决于硬件和 numpy 编译所针对的库。
在我的环境中,mutmul
(@
) 比 einsum
和 dot
:
In [27]: np.allclose(np.einsum('ijk,k',asset_returns,weights),asset_returns@weig
...: hts)
Out[27]: True
In [28]: %timeit asset_returns@weights
100 loops, best of 3: 3.91 ms per loop
In [29]: %timeit np.einsum('ijk,k',asset_returns,weights)
100 loops, best of 3: 4.73 ms per loop
In [30]: %timeit np.dot(asset_returns,weights)
100 loops, best of 3: 6.8 ms per loop
我认为时间受计算总数的限制,而不是编码细节。所有这些都将计算传递给已编译的 numpy 代码。您的原始循环版本相对较快的事实可能与循环次数少(仅 60 个)以及更完整的内存管理问题有关 dot
.
并且 numba
可能不会替换 dot
代码。
因此,在这里或那里进行调整可能会使您的代码速度提高 2 倍,但不要指望会有数量级的改进。