Die Nelder-Mead Simplex Method (vorgeschlagen von Cristián Antuña in den Kommentaren oben) bekannt ist auch eine gute Wahl zu sein für die Optimierung (posibly ungezogene) Funktionen ohne Kenntnis von Derivaten (siehe Numerical Recipies In C, Chapter 10).
Es gibt zwei etwas spezifische Aspekte zu Ihrer Frage. Die erste ist die Beschränkung der Eingaben und die zweite ist ein Skalierungsproblem. Im Folgenden werden Lösungen für diese Punkte vorgeschlagen, aber Sie müssen möglicherweise manuell einige Male zwischen ihnen durchlaufen, bis die Dinge funktionieren.
Eingangs Constraints
Ihre Eingabe Zwänge Unter der Annahme, bilden eine convex region (als Beispiele oben zeigen, aber ich möchte es ein wenig verallgemeinern), dann können Sie eine Funktion
is_in_bounds(p):
# Return if p is in the bounds
schreiben
Mit dieser Funktion wird angenommen, dass der Algorithmus vom Punkt from_
zum Punkt to
wechseln möchte, von dem bekannt ist, dass from_
in der Region liegt. Dann wird die folgende Funktion effizient den äußersten Punkt auf der Linie finden zwischen den beiden Punkten, auf dem sie gehen können:
from numpy.linalg import norm
def progress_within_bounds(from_, to, eps):
"""
from_ -- source (in region)
to -- target point
eps -- Eucliedan precision along the line
"""
if norm(from_, to) < eps:
return from_
mid = (from_ + to)/2
if is_in_bounds(mid):
return progress_within_bounds(mid, to, eps)
return progress_within_bounds(from_, mid, eps)
(Beachten Sie, dass diese Funktion für einige Regionen optimiert werden kann, aber es ist kaum lohnt sich die Mühe, als es nennt nicht einmal Ihre ursprüngliche Objektfunktion, die die teure ist.)
Einer der netten Aspekte von Nelder-Mead ist, dass die Funktion eine Reihe von Schritten ausführt, die so intuitiv sind. Einige dieser Punkte können Sie offensichtlich aus der Region werfen, aber es ist einfach, dies zu ändern. Hier ist ein implementation of Nelder Mead mit Modifikationen zwischen Paaren von Zeilen der Form markiert ##################################################################
:
import copy
'''
Pure Python/Numpy implementation of the Nelder-Mead algorithm.
Reference: https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method
'''
def nelder_mead(f, x_start,
step=0.1, no_improve_thr=10e-6, no_improv_break=10, max_iter=0,
alpha = 1., gamma = 2., rho = -0.5, sigma = 0.5):
'''
@param f (function): function to optimize, must return a scalar score
and operate over a numpy array of the same dimensions as x_start
@param x_start (numpy array): initial position
@param step (float): look-around radius in initial step
@no_improv_thr, no_improv_break (float, int): break after no_improv_break iterations with
an improvement lower than no_improv_thr
@max_iter (int): always break after this number of iterations.
Set it to 0 to loop indefinitely.
@alpha, gamma, rho, sigma (floats): parameters of the algorithm
(see Wikipedia page for reference)
'''
# init
dim = len(x_start)
prev_best = f(x_start)
no_improv = 0
res = [[x_start, prev_best]]
for i in range(dim):
x = copy.copy(x_start)
x[i] = x[i] + step
score = f(x)
res.append([x, score])
# simplex iter
iters = 0
while 1:
# order
res.sort(key = lambda x: x[1])
best = res[0][1]
# break after max_iter
if max_iter and iters >= max_iter:
return res[0]
iters += 1
# break after no_improv_break iterations with no improvement
print '...best so far:', best
if best < prev_best - no_improve_thr:
no_improv = 0
prev_best = best
else:
no_improv += 1
if no_improv >= no_improv_break:
return res[0]
# centroid
x0 = [0.] * dim
for tup in res[:-1]:
for i, c in enumerate(tup[0]):
x0[i] += c/(len(res)-1)
# reflection
xr = x0 + alpha*(x0 - res[-1][0])
##################################################################
##################################################################
xr = progress_within_bounds(x0, x0 + alpha*(x0 - res[-1][0]), prog_eps)
##################################################################
##################################################################
rscore = f(xr)
if res[0][1] <= rscore < res[-2][1]:
del res[-1]
res.append([xr, rscore])
continue
# expansion
if rscore < res[0][1]:
xe = x0 + gamma*(x0 - res[-1][0])
##################################################################
##################################################################
xe = progress_within_bounds(x0, x0 + gamma*(x0 - res[-1][0]), prog_eps)
##################################################################
##################################################################
escore = f(xe)
if escore < rscore:
del res[-1]
res.append([xe, escore])
continue
else:
del res[-1]
res.append([xr, rscore])
continue
# contraction
xc = x0 + rho*(x0 - res[-1][0])
##################################################################
##################################################################
xc = progress_within_bounds(x0, x0 + rho*(x0 - res[-1][0]), prog_eps)
##################################################################
##################################################################
cscore = f(xc)
if cscore < res[-1][1]:
del res[-1]
res.append([xc, cscore])
continue
# reduction
x1 = res[0][0]
nres = []
for tup in res:
redx = x1 + sigma*(tup[0] - x1)
score = f(redx)
nres.append([redx, score])
res = nres
Hinweis Diese Implementierung GPL ist, die entweder gut für Sie ist oder nicht. Es ist extrem einfach, NM von irgendeinem Pseudocode zu modifizieren, und Sie könnten simulated annealing auf jeden Fall einwerfen.
Skalierung
Dies ist ein heikler Problem, aber jasaarim hat einen interessanten Punkt in Bezug auf das macht. Sobald der modifizierte NM-Algorithmus einen Punkt gefunden hat, möchten Sie möglicherweise matplotlib.contour
ausführen, während Sie einige Dimensionen korrigieren, um zu sehen, wie sich die Funktion verhält. An dieser Stelle möchten Sie möglicherweise eine oder mehrere Dimensionen neu skalieren und die geänderte NM erneut ausführen.
-
Es scheint, dass Sie mit sehr ähnlichen Werten für Ihre Funktion stecken geblieben sind, bevor Sie sogar einmal iteriert haben. Vielleicht könnte eine Methode wie Nelder-Mead (die keine Derivate verwendet) mit kleinen ftol- und xtol-Parametern aufgelöst werden. IMHO, eine Grid-Suche könnte der beste Ort sein, um damit zu beginnen (und geben Sie dann anfängliche Werte zu minimieren). Bist du sicher, dass du keine NaNs oder Infs bekommst? (Manchmal gibt es sogar theoretische Grenzen für die Parameter, es passiert, dass ein Algorithmus einen von diesen zurückgibt, wenn er nicht numerisch stabil ist) –
Das Problem ist, dass der Schritt zur Berechnung des ungefähren Gradienten zu klein sein muss, daher die Nullstellen die Jacobi-Matrix. Versuchen Sie 'options = {'epsilon': 1e-4}' mit 'method = 'L-BFGS-B'' oder einen größeren Wert (standardmäßig ist es '1e-8') zu verwenden, bis die jacobian-Matrix nicht mehr vorhanden ist Nullen. – rth
Beide Lösungen scheinen zu funktionieren. Ich würde jedoch gerne einen der drei verwenden, wo ich Constraints ('BFGS',' L-BFGS-B', 'SLSQP') verwenden kann. Also, indem ich 'eps: 1e0' einstelle, läuft es, aber irgendwann renne ich außerhalb meiner Constraints. Das einzige, was von OP hinzugefügt wird, ist ', options = {'eps': 1e + 0})' in der Funktion 'minimieren'. –