If I have N independent optimization problems, in general, would it be faster to solve each independently or combine the problems? Newton's method has time complexity log(p) * (time to calculate derivative ratio) for a given precision p, assuming reasonable seeds. If multivariate Newton Raphson time complexity does not scale with the number of dimensions, then complexity at a given p will still depend on the gradient/hessian calculations and the linear solver. I'm asking mostly for the sake of code efficiency: I want to fit likelihood functions of subsets of data with either per subset regularization or global regularization. If the two are similar in cost, I could implement one Newton optimizer that could take the various regularization penalties as functional arguments, switching between sparse and dense solvers when appropriate.
Below, the sparse solver beats the individual via loop, but I wonder if it's just python loop overhead (would I get better loop results using Cython)? The conversion from matrix stack to block diagonal is costly, but irrelevant to my implementation; I fill pre-existing gradient/hessian arrays on each iteration so the calculation time should be the same. My IDE froze when I used np.linalg.solve
with a dense matrix for bigA
, which opens up another problem that I'll probably need to find a more efficient way to solve the dense problems that will result from global regularization.
import timeit
setup = '''
import numpy as np; import scipy as sp
import scipy.sparse; import scipy.sparse.linalg
A = np.random.random((1000,10,10))
b = np.random.random((1000,10))
bigA = sp.sparse.block_diag(A, format = "csr")
bigb = b.flatten()
'''
expr1 = 'for i in range(1000): np.linalg.solve(A[i,...], b[i,...])'
expr2 = 'sp.sparse.linalg.spsolve(bigA, bigb)'
timeit.timeit(expr1, setup, number = 100)
# 1.2879039069994178
timeit.timeit(expr2, setup, number = 100)
# 0.45410968599753687
# with setup
imports = '''
import numpy as np
import scipy as sp
import scipy.sparse
import scipy.sparse.linalg
'''
setup1 = '''
A = np.random.random((1000,10,10))
b = np.random.random((1000,10))
for i in range(1000):
np.linalg.solve(A[i,...], b[i,...])
'''
setup2 = '''
A = np.random.random((1000,10,10))
b = np.random.random((1000,10))
bigA = sp.sparse.block_diag(A, format = "csr")
bigb = b.flatten()
sp.sparse.linalg.spsolve(bigA, bigb)
'''
timeit.timeit(setup1, imports, number = 100)
# 1.355804075999913
timeit.timeit(setup2, imports, number = 100)
# 24.209087412000372
sol1 = np.empty(1e4)
u = 0
for i in range(1000):
sol1[u:u+10] = np.linalg.solve(A[i,...], b[i,...])
u += 10
sol2 = sp.sparse.linalg.spsolve(bigA, bigb)
np.sum((sol1 - sol2)**2)
# 2.49782849627e-14