ValueError: The user-provided objective function must return a scalar value" on scalar-valued function

Viewed 24

I'm having a problem understanding why my code continues to return a value error when the function is sure to always provide a scalar value. The function values after each run seem to only contain scalar values or Sympy constants.

This is my code:

from sympy import Symbol
from sympy.stats import Normal, density, sample_iter
from scipy.optimize import minimize
from sympy import Sum, binomial, exp, simplify
def my_func_v(args):
    print(args)
    N = Normal("N", args[0], args[1])
    y = Symbol("y", real = True)
    samples = list(sample_iter(N, numsamples=200))
    sampleSum = sum(samples)
    print("Gotten samples")
    func = simplify((Sum(sum([simplify(binomial(20,y)*(1/(1+exp(-sample)))**y*(exp(-sample)/(1+exp(-sample)))**(20-y)*density(N)(sample))/sampleSum for sample in samples]), (y, 0, 10)).doit() - 0.8)**2)
    print(f"Done with func with end value: {func}")
    return func
print("Now optimizing")
results = minimize(my_func_v, [1,1], method='BFGS', options={"disp": True, "return_all": True})

This is the return output Output from running code

1 Answers

I suspect that the error gets raised because your function returns a symbolic expression, and scipy doesn't know what to do with it. As you can see from the error, the result of the function contains additions, multiplications, radical... You could try to write return func.n(), but I don't know if that's going to work: in fact, your function is so slow to evaluate that I suggest to completely switch to a purely numerical approach.

Scipy's minimize (and every other scipy function) requires numerical data to work properly. Hence, we need to rewrite your symbolic expression as a numerical function which will return a numeric value. This, in turn, will results in an faster execution :)

Let's consider your expression:

N = Normal("N", args[0], args[1])
y = Symbol("y", real = True)
samples = list(sample_iter(N, numsamples=200))
sampleSum = sum(samples)
func = simplify((Sum(sum([simplify(binomial(20,y)*(1/(1+exp(-sample)))**y*(exp(-sample)/(1+exp(-sample)))**(20-y)*density(N)(sample))/sampleSum for sample in samples]), (y, 0, 10)).doit() - 0.8)**2)
  • sample can be generated with numpy.random.normal
  • binomial(20,y) can be lambdified (convert to a numerical function).
  • density(N)(sample) can be replaced with norm.pdf from scipy.stats.
  • the two sums can be done without the need of symbolic objects.
from sympy import Symbol, binomial, lambdify
from scipy.stats import norm
from scipy.optimize import minimize
import numpy as np

y = Symbol("y", real = True)
# create a numerical function to compute the binomial
binomial_f = lambdify([y], binomial(20, y))

def inner_sum(y, args):
    samples = np.random.normal(args[0], args[1], 200)
    sampleSum = sum(samples)
    return sum([(binomial_f(y) * (1/(1+np.exp(-sample)))**y*(np.exp(-sample)/(1+np.exp(-sample)))**(20-y)*norm.pdf(sample, args[0], args[1])) / sampleSum for sample in samples])

def outer_sum(args):
    val = (sum([inner_sum(y, args) for y in range(11)]) - 0.8)**2
    print(*args, val)
    return val

results = minimize(outer_sum, [1,1], method='BFGS', options={"disp": True, "return_all": True})
print(results)

# Warning: Desired error not necessarily achieved due to precision loss.
#          Current function value: 0.570869
#          Iterations: 1
#          Function evaluations: 68
#          Gradient evaluations: 19
#   allvecs: [array([1., 1.]), array([1.00000958, 0.99998663])]
#       fun: 0.5708685806309425
#  hess_inv: array([[1.35282892e-03, 6.12645895e-02],
#        [6.12645895e-02, 2.77444541e+00]])
#       jac: array([129387.86052179, 454897.50102044])
#   message: 'Desired error not necessarily achieved due to precision loss.'
#      nfev: 68
#       nit: 1
#      njev: 19
#    status: 2
#   success: False
#         x: array([1.00000958, 0.99998663])

Related