2016-12-14 12 views
3

Я запускаю большую программу Python для оптимизации портфельных весов для оптимизации портфеля (Markowitz) в финансах. Когда я просматриваю код, 90% времени выполнения затрачивается на расчет возврата портфеля, что делается миллионы раз. Что я могу сделать, чтобы ускорить мой код? Я попытался:Как ускорить профилированный код NumPy - векторизация, Numba?

  • векторизации расчет возвращается: сделал код медленнее, от 1,5 мс до 3 мс
  • использовал функцию autojit от Numba ускорить код: отсутствие изменений

См. Пример ниже - любые предложения?

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) 

EDIT

Решение: Matmul был самым быстрым на моей машине:

def get_pf_returns(weights, asset_returns): 
    return np.prod(1 + np.matmul(asset_returns, weights), axis=0) ** (12.0/60) - 1 

ответ

2

В моей среде, mutmul (@) имеет скромное преимущество времени над einsum и dot:

In [27]: np.allclose(np.einsum('ijk,k',asset_returns,weights),[email protected] 
    ...: hts) 
Out[27]: True 
In [28]: %timeit [email protected] 
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 раза, но не ожидайте улучшения порядка.

+0

Спасибо! Это большая помощь, чтобы знать, чего я могу ожидать, тогда я рассмотрю остальную часть кода. –

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 скомпилированы.