簡體   English   中英

如何加速分析NumPy代碼 - 矢量化,Numba?

[英]How do I speed up profiled NumPy code - vectorizing, Numba?

我正在運行一個大型Python程序來優化金融中(Markowitz)投資組合優化的投資組合權重。 當我對代碼進行概要分析時,90%的運行時間用於計算投資組合回報,這已經完成了數百萬次。 我該怎么做才能加快我的代碼速度? 我試過了:

  • 矢量化返回計算:使代碼變慢 ,從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

在我的環境中, mutmul@ )比einsumdot具有適度的時間優勢:

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倍,但不要指望有一個數量級的改進。

這是一個使用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庫。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM