¿Cómo puedo acelerar el código NumPy perfilado – vectorización, Numba?

Estoy ejecutando un gran progtwig Python para optimizar las ponderaciones de la cartera para la optimización de la cartera (Markowitz) en finanzas. Cuando hago un perfil del código, el 90% del tiempo de ejecución se invierte en calcular la rentabilidad de la cartera, que se realiza millones de veces. ¿Qué puedo hacer para acelerar mi código? Yo he tratado:

  • Vectorización del cálculo de rendimientos: hizo el código más lento , de 1.5 ms a 3 ms.
  • usé la función autojit de Numba para acelerar el código: sin cambios

Vea el ejemplo a continuación, ¿alguna sugerencia?

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) 

EDITAR

Solución: Matmul fue el más rápido en mi máquina:

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

En mi entorno, mutmul ( @ ) tiene una modesta ventaja de tiempo sobre einsum y 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 

Creo que los tiempos están limitados por el número total de cálculos, más que los detalles de encoding. Todos estos pasan el cálculo al código comstackdo numpy. El hecho de que su versión original en bucle sea relativamente rápida probablemente tenga que ver con el pequeño número de bucles (solo 60) y los problemas de administración de memoria en el dot más completo.

Y numba probablemente no está reemplazando el código de dot .

Por lo tanto, un ajuste aquí o allá podría acelerar su código en un factor de 2, pero no espere una mejora de un orden de magnitud.

Aquí hay una versión que usa np.einsum para obtener un poco de aceleración:

 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 

Y luego los tiempos:

 %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 

Los tiempos en su máquina podrían ser diferentes dependiendo del hardware y el número de bibliotecas está comstackdo.